diff --git a/package-lock.json b/package-lock.json
index 6ca4890..0e66a25 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.15",
"license": "MIT",
"dependencies": {
+ "chokidar": "^3.6.0",
"glob": "^10.3.10",
"log-update": "^6.0.0",
"oo-ascii-tree": "^1.91.0"
@@ -1454,7 +1455,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
- "dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -1689,7 +1689,6 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -1768,12 +1767,11 @@
}
},
"node_modules/braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -2074,7 +2072,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
- "dev": true,
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -3451,10 +3448,9 @@
}
},
"node_modules/fill-range": {
- "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,
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -3648,7 +3644,6 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -3820,7 +3815,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,
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -4553,10 +4547,23 @@
"node": ">= 0.10"
}
},
- "node_modules/ip": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
- "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
+ "node_modules/ip-address": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
+ "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
+ "dev": true,
+ "dependencies": {
+ "jsbn": "1.1.0",
+ "sprintf-js": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ip-address/node_modules/sprintf-js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"dev": true
},
"node_modules/is-alphabetical": {
@@ -4635,7 +4642,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@@ -4748,7 +4754,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4765,7 +4770,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"
},
@@ -4862,7 +4866,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"
}
@@ -5172,6 +5175,12 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsbn": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
+ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
+ "dev": true
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -5919,7 +5928,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -6258,13 +6266,12 @@
}
},
"node_modules/pac-resolver": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz",
- "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
+ "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
"dev": true,
"dependencies": {
"degenerator": "^5.0.0",
- "ip": "^1.1.8",
"netmask": "^2.0.2"
},
"engines": {
@@ -6497,7 +6504,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
"engines": {
"node": ">=8.6"
},
@@ -6754,7 +6760,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@@ -7428,16 +7433,16 @@
}
},
"node_modules/socks": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
- "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
+ "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"dev": true,
"dependencies": {
- "ip": "^2.0.0",
+ "ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
},
"engines": {
- "node": ">= 10.13.0",
+ "node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
@@ -7455,12 +7460,6 @@
"node": ">= 14"
}
},
- "node_modules/socks/node_modules/ip": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
- "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==",
- "dev": true
- },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -7755,7 +7754,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"
},
@@ -8369,9 +8367,9 @@
}
},
"node_modules/ws": {
- "version": "8.14.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
- "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"engines": {
"node": ">=10.0.0"
@@ -9477,7 +9475,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
- "dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -9637,8 +9634,7 @@
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
- "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
- "dev": true
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
},
"bl": {
"version": "5.1.0",
@@ -9701,12 +9697,11 @@
}
},
"braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"requires": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
}
},
"buffer": {
@@ -9909,7 +9904,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
- "dev": true,
"requires": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -10877,10 +10871,9 @@
}
},
"fill-range": {
- "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,
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"requires": {
"to-regex-range": "^5.0.1"
}
@@ -11030,7 +11023,6 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
"optional": true
},
"function-bind": {
@@ -11169,7 +11161,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"
}
@@ -11657,11 +11648,23 @@
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
"dev": true
},
- "ip": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
- "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
- "dev": true
+ "ip-address": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
+ "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
+ "dev": true,
+ "requires": {
+ "jsbn": "1.1.0",
+ "sprintf-js": "^1.1.3"
+ },
+ "dependencies": {
+ "sprintf-js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+ "dev": true
+ }
+ }
},
"is-alphabetical": {
"version": "2.0.1",
@@ -11719,7 +11722,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
@@ -11788,8 +11790,7 @@
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
@@ -11800,7 +11801,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"
}
@@ -11857,8 +11857,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-number-object": {
"version": "1.0.7",
@@ -12069,6 +12068,12 @@
"esprima": "^4.0.0"
}
},
+ "jsbn": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
+ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
+ "dev": true
+ },
"json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -12617,8 +12622,7 @@
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
},
"normalize-url": {
"version": "8.0.0",
@@ -12848,13 +12852,12 @@
}
},
"pac-resolver": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz",
- "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
+ "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
"dev": true,
"requires": {
"degenerator": "^5.0.0",
- "ip": "^1.1.8",
"netmask": "^2.0.2"
}
},
@@ -13028,8 +13031,7 @@
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
"pify": {
"version": "2.3.0",
@@ -13219,7 +13221,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
"requires": {
"picomatch": "^2.2.1"
}
@@ -13695,21 +13696,13 @@
"dev": true
},
"socks": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
- "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
+ "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"dev": true,
"requires": {
- "ip": "^2.0.0",
+ "ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
- },
- "dependencies": {
- "ip": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
- "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==",
- "dev": true
- }
}
},
"socks-proxy-agent": {
@@ -13932,7 +13925,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"
}
@@ -14388,9 +14380,9 @@
}
},
"ws": {
- "version": "8.14.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
- "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"requires": {}
},
diff --git a/package.json b/package.json
index 5152df3..460fd02 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
"typedoc": "^0.25.12"
},
"dependencies": {
+ "chokidar": "^3.6.0",
"glob": "^10.3.10",
"log-update": "^6.0.0",
"oo-ascii-tree": "^1.91.0"
diff --git a/src/cli.js b/src/cli.js
index adf248c..50883a8 100755
--- a/src/cli.js
+++ b/src/cli.js
@@ -4,6 +4,9 @@ import { globSync } from "glob";
import env from "./env/node.js";
import run from "./run.js";
+// Dependencies
+import * as chokidar from "chokidar";
+
const CONFIG_GLOB = "{,_,.}htest.{json,config.json,config.js}";
let config;
@@ -50,5 +53,16 @@ export default async function cli (options = {}) {
options.path = argv[1];
}
+ if (options.watch) {
+ const watcher = chokidar.watch(location, {
+ persistent: true,
+ });
+
+ // TODO: Re-run the tests that *actually* changed
+ watcher.on("change", path => {
+ run(location, {env, ...options});
+ });
+ }
+
run(location, {env, ...options});
}
\ No newline at end of file
diff --git a/src/env/node.js b/src/env/node.js
index 9753be8..982b632 100644
--- a/src/env/node.js
+++ b/src/env/node.js
@@ -1,16 +1,18 @@
// Native Node packages
import fs from "fs";
import path from "path";
-import * as readline from 'node:readline';
+import process from "node:process";
+import * as readline from "node:readline";
// Dependencies
-import logUpdate from 'log-update';
-import { AsciiTree } from 'oo-ascii-tree';
-import { globSync } from 'glob';
+import logUpdate from "log-update";
+import { AsciiTree } from "oo-ascii-tree";
+import { globSync } from "glob";
// Internal modules
import format from "../format-console.js";
-import { getType } from '../util.js';
+import { getType } from "../util.js";
+import run from "../run.js";
/**
* Recursively traverse a subtree starting from `node`
@@ -91,20 +93,33 @@ async function getTestsIn (dir) {
let cwd = process.cwd();
let paths = filenames.map(name => path.join(cwd, dir, name));
- return Promise.all(paths.map(path => import(path).then(module => module.default, err => {
+ // FIXME: Causes a memory leak. Any other workaround?
+ return Promise.all(paths.map(path => import(`${path}?${Date.now()}`).then(module => module.default, err => {
console.error(`Error importing tests from ${path}:`, err);
})));
}
+/**
+ * Options shared between runs of the tests.
+ */
+let shared = {
+ firstRun: true,
+ location: undefined, // location of the tests
+ root: undefined,
+ active: undefined, // active (highlighted) group of tests that can be expanded/collapsed; root by default
+};
+
export default {
name: "Node.js",
defaultOptions: {
format: "rich",
get location () {
return process.cwd();
- }
+ },
},
resolveLocation: async function (location) {
+ shared.location = location;
+
if (fs.statSync(location).isDirectory()) {
// Directory provided, fetch all files
return getTestsIn(location);
@@ -116,7 +131,7 @@ export default {
paths = getType(paths) === "string" ? [paths] : paths;
return paths.map(p => {
p = path.join(process.cwd(), p);
- return import(p).then(m => m.default ?? m);
+ return import(`${p}?${Date.now()}`).then(m => m.default ?? m); // FIXME: Causes a memory leak. Any other workaround?
});
});
@@ -134,129 +149,142 @@ export default {
if (root.stats.pending === 0) {
logUpdate.clear();
- let hint = `
+ root.highlighted = true;
+ shared.root = root;
+ shared.active = root;
+
+ if (!shared.firstRun) {
+ render(root, options);
+ }
+ else {
+ shared.firstRun = false;
+
+ let hint = `
Use ↑ and ↓ arrow keys to navigate groups of tests, → and ← to expand and collapse them, respectively.
Use Ctrl+↑ and Ctrl+↓ to go to the first or last child group of the current group.
To expand or collapse the current group and all its subgroups, use Ctrl+→ and Ctrl+←.
Press Ctrl+Shift+→ and Ctrl+Shift+← to expand or collapse all groups, regardless of the current group.
-Use any other key to quit interactive mode.
+Press R to re-run the tests. Use any other key to quit interactive mode.
`;
- hint = format(hint);
- console.log(hint);
+ hint = format(hint);
+ console.log(hint);
- readline.emitKeypressEvents(process.stdin);
- process.stdin.setRawMode(true); // handle keypress events instead of Node
+ readline.emitKeypressEvents(process.stdin);
+ process.stdin.setRawMode(true); // handle keypress events instead of Node
- root.highlighted = true;
- render(root, options);
+ render(root, options);
- let active = root; // active (highlighted) group of tests that can be expanded/collapsed; root by default
- process.stdin.on("keypress", (character, key) => {
- let name = key.name;
+ process.stdin.on("keypress", (character, key) => {
+ let name = key.name;
+ let root = shared.root;
- if (name === "up") {
- // Figure out what group of tests is active (and should be highlighted)
- let groups = getVisibleGroups(root, options);
+ if (name === "up") {
+ // Figure out what group of tests is active (and should be highlighted)
+ let groups = getVisibleGroups(root, options);
- if (key.ctrl) {
- let parent = active.parent;
- if (parent) {
- active = groups.filter(group => group.parent === parent)[0]; // the first one from all groups with the same parent
+ if (key.ctrl) {
+ let parent = shared.active.parent;
+ if (parent) {
+ shared.active = groups.filter(group => group.parent === parent)[0]; // the first one from all groups with the same parent
+ }
}
- }
- else {
- let index = groups.indexOf(active);
- index = Math.max(0, index - 1); // choose the previous group, but don't go higher than the root
- active = groups[index];
- }
-
- for (let group of groups) {
- group.highlighted = false;
- }
- active.highlighted = true;
- render(root, options);
- }
- else if (name === "down") {
- let groups = getVisibleGroups(root, options);
-
- if (key.ctrl) {
- let parent = active.parent;
- if (parent) {
- active = groups.filter(group => group.parent === parent).at(-1); // the last one from all groups with the same parent
+ else {
+ let index = groups.indexOf(shared.active);
+ index = Math.max(0, index - 1); // choose the previous group, but don't go higher than the root
+ shared.active = groups[index];
}
- }
- else {
- let index = groups.indexOf(active);
- index = Math.min(groups.length - 1, index + 1); // choose the next group, but don't go lower than the last one
- active = groups[index];
- }
- for (let group of groups) {
- group.highlighted = false;
- }
- active.highlighted = true;
- render(root, options);
- }
- else if (name === "left") {
- if (key.ctrl && key.shift) {
- // Collapse all groups on Ctrl+Shift+←
- let groups = getVisibleGroups(root, options);
for (let group of groups) {
group.highlighted = false;
}
-
- setCollapsed(root);
- active = root;
- active.highlighted = true;
- render(root, options);
- }
- else if (key.ctrl) {
- // Collapse the current group and all its subgroups on Ctrl+←
- setCollapsed(active);
- render(root, options);
- }
- else if (active.collapsed === false) {
- active.collapsed = true;
+ shared.active.highlighted = true;
render(root, options);
}
- else if (active.parent) {
- // If the current group is collapsed, collapse its parent group
+ else if (name === "down") {
let groups = getVisibleGroups(root, options);
- let index = groups.indexOf(active.parent);
- active = groups[index];
- active.collapsed = true;
- groups = groups.map(group => group.highlighted = false);
- active.highlighted = true;
+ if (key.ctrl) {
+ let parent = shared.active.parent;
+ if (parent) {
+ shared.active = groups.filter(group => group.parent === parent).at(-1); // the last one from all groups with the same parent
+ }
+ }
+ else {
+ let index = groups.indexOf(shared.active);
+ index = Math.min(groups.length - 1, index + 1); // choose the next group, but don't go lower than the last one
+ shared.active = groups[index];
+ }
+
+ for (let group of groups) {
+ group.highlighted = false;
+ }
+ shared.active.highlighted = true;
render(root, options);
}
- }
- else if (name === "right") {
- if (key.ctrl && key.shift) {
- // Expand all groups on Ctrl+Shift+→
- setCollapsed(root, false);
- render(root, options);
+ else if (name === "left") {
+ if (key.ctrl && key.shift) {
+ // Collapse all groups on Ctrl+Shift+←
+ let groups = getVisibleGroups(root, options);
+ for (let group of groups) {
+ group.highlighted = false;
+ }
+
+ setCollapsed(root);
+ shared.active = root;
+ shared.active.highlighted = true;
+ render(root, options);
+ }
+ else if (key.ctrl) {
+ // Collapse the current group and all its subgroups on Ctrl+←
+ setCollapsed(shared.active);
+ render(root, options);
+ }
+ else if (shared.active.collapsed === false) {
+ shared.active.collapsed = true;
+ render(root, options);
+ }
+ else if (shared.active.parent) {
+ // If the current group is collapsed, collapse its parent group
+ let groups = getVisibleGroups(root, options);
+ let index = groups.indexOf(shared.active.parent);
+ shared.active = groups[index];
+ shared.active.collapsed = true;
+
+ groups = groups.map(group => group.highlighted = false);
+ shared.active.highlighted = true;
+ render(root, options);
+ }
}
- else if (key.ctrl) {
- // Expand the current group and all its subgroups on Ctrl+→
- setCollapsed(active, false);
- render(root, options);
+ else if (name === "right") {
+ if (key.ctrl && key.shift) {
+ // Expand all groups on Ctrl+Shift+→
+ setCollapsed(root, false);
+ render(root, options);
+ }
+ else if (key.ctrl) {
+ // Expand the current group and all its subgroups on Ctrl+→
+ setCollapsed(shared.active, false);
+ render(root, options);
+ }
+ else if (shared.active.collapsed === true) {
+ shared.active.collapsed = false;
+ render(root, options);
+ }
}
- else if (active.collapsed === true) {
- active.collapsed = false;
- render(root, options);
+ else if (name === "r") {
+ run(shared.location, {...options});
}
- }
- else {
- // Quit interactive mode on any other key
- logUpdate.done();
- process.exit();
- }
- });
+ else {
+ // Quit interactive mode on any other key
+ logUpdate.done();
+ process.exit();
+ }
+ });
+ }
}
if (root.stats.fail > 0) {
process.exitCode = 1;
}
- }
-}
+ },
+};