@vbarbarosh/node-helpers 3.72.4 → 3.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/browser.js +208 -228
- package/package.json +1 -1
- package/src/array_chunk_jobs.js +37 -0
- package/src/array_chunk_jobs.test.js +37 -0
- package/src/array_fps.js +20 -0
- package/src/array_fps.test.js +11 -0
- package/src/browser/index.js +1 -1
- package/src/clamp.js +6 -0
- package/src/cli-apps/watchdog.d/quick-exit-after-2-pings.js +15 -0
- package/src/cli-apps/watchdog.d/quick-hang-after-1-ping.js +15 -0
- package/src/cli-apps/watchdog.js +12 -10
- package/src/cli-apps/watchdog.test.js +43 -2
- package/src/format_ms3.js +2 -2
- package/src/fs_lsr.js +3 -0
- package/src/fs_walk.js +40 -0
- package/src/round.js +101 -0
- package/src/round.test.js +56 -0
- package/src/shell.md +16 -0
- package/src/shell_json.md +12 -0
- package/src/shell_lines.md +12 -0
- package/src/shell_spawn.js +35 -35
- package/src/shell_spawn.md +25 -0
- package/src/shell_spawn.test.js +8 -1
- package/src/shell_stdall.md +11 -0
- package/src/shell_thru.md +10 -0
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npm install @vbarbarosh/node-helpers
|
|
|
20
20
|
A subset of `@vbarbarosh/node-helpers` is available for browser:
|
|
21
21
|
|
|
22
22
|
```html
|
|
23
|
-
<script src="https://unpkg.com/@vbarbarosh/node-helpers@3.
|
|
23
|
+
<script src="https://unpkg.com/@vbarbarosh/node-helpers@3.73.0/dist/browser.js"></script>
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
⚠️ All browser functions are exposed globally.
|
package/dist/browser.js
CHANGED
|
@@ -72,14 +72,16 @@ function serializer(replacer, cycleReplacer) {
|
|
|
72
72
|
|
|
73
73
|
/***/ }),
|
|
74
74
|
|
|
75
|
-
/***/ "./src sync recursive
|
|
76
|
-
|
|
77
|
-
!*** ./src/ sync
|
|
78
|
-
|
|
75
|
+
/***/ "./src sync recursive ^(?%21.*(cli-apps%7Cbrowser%7C\\.d)\\/).*\\b(array%7Cdate%7Chttp_delete%7Chttp_get_blob%7Chttp_get_buffer%7Chttp_get_json%7Chttp_get_utf8%7Chttp_head%7Chttp_patch_json%7Chttp_post_json%7Chttp_post_multipart%7Chttp_put_buffer%7Chttp_put_json%7Chttp_put_utf8%7Cidentity%7Cignore%7Cfcmp%7Cfilter%7Cformat%7Cplural%7Crandom_int%7Curlmod%7Cwaitcb)[^/]*(?<%21\\.test)\\.js$":
|
|
76
|
+
/*!**********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
|
|
77
|
+
!*** ./src/ sync ^(?%21.*(cli-apps%7Cbrowser%7C\.d)\/).*\b(array%7Cdate%7Chttp_delete%7Chttp_get_blob%7Chttp_get_buffer%7Chttp_get_json%7Chttp_get_utf8%7Chttp_head%7Chttp_patch_json%7Chttp_post_json%7Chttp_post_multipart%7Chttp_put_buffer%7Chttp_put_json%7Chttp_put_utf8%7Cidentity%7Cignore%7Cfcmp%7Cfilter%7Cformat%7Cplural%7Crandom_int%7Curlmod%7Cwaitcb)[^/]*(?<%21\.test)\.js$ ***!
|
|
78
|
+
\**********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
|
|
79
79
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
80
80
|
|
|
81
81
|
var map = {
|
|
82
82
|
"./array_chunk.js": "./src/array_chunk.js",
|
|
83
|
+
"./array_chunk_jobs.js": "./src/array_chunk_jobs.js",
|
|
84
|
+
"./array_fps.js": "./src/array_fps.js",
|
|
83
85
|
"./array_gcd.js": "./src/array_gcd.js",
|
|
84
86
|
"./array_group.js": "./src/array_group.js",
|
|
85
87
|
"./array_group_map.js": "./src/array_group_map.js",
|
|
@@ -94,8 +96,6 @@ var map = {
|
|
|
94
96
|
"./array_sum.js": "./src/array_sum.js",
|
|
95
97
|
"./array_unique.js": "./src/array_unique.js",
|
|
96
98
|
"./array_unique_last.js": "./src/array_unique_last.js",
|
|
97
|
-
"./cli-apps/watchdog.d/ignore_sigterm.js": "./src/cli-apps/watchdog.d/ignore_sigterm.js",
|
|
98
|
-
"./cli-apps/watchdog.d/my-ignore-sigterm.js": "./src/cli-apps/watchdog.d/my-ignore-sigterm.js",
|
|
99
99
|
"./date_add_hours.js": "./src/date_add_hours.js",
|
|
100
100
|
"./date_add_milliseconds.js": "./src/date_add_milliseconds.js",
|
|
101
101
|
"./date_add_minutes.js": "./src/date_add_minutes.js",
|
|
@@ -146,8 +146,6 @@ var map = {
|
|
|
146
146
|
"./http_put_utf8.js": "./src/http_put_utf8.js",
|
|
147
147
|
"./identity.js": "./src/identity.js",
|
|
148
148
|
"./ignore.js": "./src/ignore.js",
|
|
149
|
-
"./pid_kill_grace.d/ignore-sigterm-for-10ms.js": "./src/pid_kill_grace.d/ignore-sigterm-for-10ms.js",
|
|
150
|
-
"./pid_kill_grace.d/ignore-sigterm.js": "./src/pid_kill_grace.d/ignore-sigterm.js",
|
|
151
149
|
"./plural.js": "./src/plural.js",
|
|
152
150
|
"./random_int.js": "./src/random_int.js",
|
|
153
151
|
"./urlmod.js": "./src/urlmod.js",
|
|
@@ -172,7 +170,7 @@ webpackContext.keys = function webpackContextKeys() {
|
|
|
172
170
|
};
|
|
173
171
|
webpackContext.resolve = webpackContextResolve;
|
|
174
172
|
module.exports = webpackContext;
|
|
175
|
-
webpackContext.id = "./src sync recursive
|
|
173
|
+
webpackContext.id = "./src sync recursive ^(?%21.*(cli-apps%7Cbrowser%7C\\.d)\\/).*\\b(array%7Cdate%7Chttp_delete%7Chttp_get_blob%7Chttp_get_buffer%7Chttp_get_json%7Chttp_get_utf8%7Chttp_head%7Chttp_patch_json%7Chttp_post_json%7Chttp_post_multipart%7Chttp_put_buffer%7Chttp_put_json%7Chttp_put_utf8%7Cidentity%7Cignore%7Cfcmp%7Cfilter%7Cformat%7Cplural%7Crandom_int%7Curlmod%7Cwaitcb)[^/]*(?<%21\\.test)\\.js$";
|
|
176
174
|
|
|
177
175
|
/***/ }),
|
|
178
176
|
|
|
@@ -205,6 +203,83 @@ function array_chunk(array = [], limit = 1)
|
|
|
205
203
|
module.exports = array_chunk;
|
|
206
204
|
|
|
207
205
|
|
|
206
|
+
/***/ }),
|
|
207
|
+
|
|
208
|
+
/***/ "./src/array_chunk_jobs.js":
|
|
209
|
+
/*!*********************************!*\
|
|
210
|
+
!*** ./src/array_chunk_jobs.js ***!
|
|
211
|
+
\*********************************/
|
|
212
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
213
|
+
|
|
214
|
+
const clamp = __webpack_require__(/*! ./clamp */ "./src/clamp.js");
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Split an array of jobs between up to `max_threads`, while trying to put at least
|
|
218
|
+
* `min_jobs_per_thread` items into each chunk.
|
|
219
|
+
*
|
|
220
|
+
* - If an array is empty → returns [].
|
|
221
|
+
* - If there are too few items to satisfy `min_items_per_chunk`, even for 1 chunk,
|
|
222
|
+
* everything goes into a single chunk.
|
|
223
|
+
*
|
|
224
|
+
* Alternative names:
|
|
225
|
+
* - array_chunk_balanced
|
|
226
|
+
*
|
|
227
|
+
* @param array
|
|
228
|
+
* @param max_threads
|
|
229
|
+
* @param min_jobs_per_thread
|
|
230
|
+
* @returns {*[]}
|
|
231
|
+
*/
|
|
232
|
+
function array_chunk_jobs(array = [], max_threads = 1, min_jobs_per_thread = 1)
|
|
233
|
+
{
|
|
234
|
+
const total_threads = clamp(1, max_threads, Math.floor(array.length / min_jobs_per_thread));
|
|
235
|
+
const jobs_per_thread = Math.floor(array.length / total_threads);
|
|
236
|
+
let extra = array.length % total_threads;
|
|
237
|
+
|
|
238
|
+
const out = [];
|
|
239
|
+
for (let i = 0; i < array.length; ) {
|
|
240
|
+
const size = jobs_per_thread + (extra ? 1 : 0);
|
|
241
|
+
out.push(array.slice(i, i + size));
|
|
242
|
+
i += size;
|
|
243
|
+
if (extra) {
|
|
244
|
+
extra--;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = array_chunk_jobs;
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
/***/ }),
|
|
254
|
+
|
|
255
|
+
/***/ "./src/array_fps.js":
|
|
256
|
+
/*!**************************!*\
|
|
257
|
+
!*** ./src/array_fps.js ***!
|
|
258
|
+
\**************************/
|
|
259
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
260
|
+
|
|
261
|
+
const round = __webpack_require__(/*! ./round */ "./src/round.js");
|
|
262
|
+
|
|
263
|
+
const EPS = 0.00001;
|
|
264
|
+
|
|
265
|
+
function array_fps(duration_sec, fps)
|
|
266
|
+
{
|
|
267
|
+
const total_frames = Math.floor(duration_sec * fps);
|
|
268
|
+
|
|
269
|
+
if (total_frames <= 1) {
|
|
270
|
+
return [0];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const out = [];
|
|
274
|
+
for (let i = 0; i < total_frames; ++i) {
|
|
275
|
+
out.push(round((i / (total_frames - 1)) * (duration_sec - EPS), EPS));
|
|
276
|
+
}
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
module.exports = array_fps;
|
|
281
|
+
|
|
282
|
+
|
|
208
283
|
/***/ }),
|
|
209
284
|
|
|
210
285
|
/***/ "./src/array_gcd.js":
|
|
@@ -650,112 +725,18 @@ module.exports = array_unique_last;
|
|
|
650
725
|
|
|
651
726
|
/***/ }),
|
|
652
727
|
|
|
653
|
-
/***/ "./src/
|
|
654
|
-
|
|
655
|
-
!*** ./src/
|
|
656
|
-
|
|
657
|
-
/***/ (() => {
|
|
658
|
-
|
|
659
|
-
//#!/usr/bin/env node
|
|
660
|
-
|
|
661
|
-
// 1️⃣ Client that ignores SIGTERM
|
|
662
|
-
// Goal: process stays alive after SIGTERM and only dies on SIGKILL (exactly what pid_kill_grace should handle).
|
|
663
|
-
//
|
|
664
|
-
// What this tests for watchdog:
|
|
665
|
-
// - watchdog sends SIGTERM → process prints “ignored” and keeps running.
|
|
666
|
-
// - pid_kill_grace waits grace_timeout_ms, sees PID still alive, sends SIGKILL.
|
|
667
|
-
// - Process must die after SIGKILL.
|
|
668
|
-
// - Watchdog should log the whole sequence correctly.
|
|
669
|
-
// This is a realistic scenario.
|
|
670
|
-
|
|
671
|
-
console.log('PID:', process.pid);
|
|
672
|
-
|
|
673
|
-
// Ignore SIGTERM
|
|
674
|
-
process.on('SIGTERM', function () {
|
|
675
|
-
console.log('SIGTERM received, but intentionally ignored');
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
// Still react to SIGINT (Ctrl+C) so you can stop it manually if needed
|
|
679
|
-
process.on('SIGINT', function () {
|
|
680
|
-
console.log('SIGINT received, exiting');
|
|
681
|
-
process.exit(130);
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
// Keep process alive
|
|
685
|
-
setInterval(() => {
|
|
686
|
-
// simulate work
|
|
687
|
-
}, 1000);
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
/***/ }),
|
|
691
|
-
|
|
692
|
-
/***/ "./src/cli-apps/watchdog.d/my-ignore-sigterm.js":
|
|
693
|
-
/*!******************************************************!*\
|
|
694
|
-
!*** ./src/cli-apps/watchdog.d/my-ignore-sigterm.js ***!
|
|
695
|
-
\******************************************************/
|
|
696
|
-
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
|
|
697
|
-
|
|
698
|
-
//#!/usr/bin/env node
|
|
699
|
-
|
|
700
|
-
const Promise = __webpack_require__(/*! bluebird */ "bluebird");
|
|
701
|
-
const cli = __webpack_require__(/*! @vbarbarosh/node-helpers/src/cli */ "./src/cli.js");
|
|
702
|
-
const now_human = __webpack_require__(/*! @vbarbarosh/node-helpers/src/now_human */ "./src/now_human.js");
|
|
703
|
-
|
|
704
|
-
cli(main);
|
|
705
|
-
|
|
706
|
-
async function main()
|
|
707
|
-
{
|
|
708
|
-
process.on('SIGTERM', function () {
|
|
709
|
-
console.log(`[${now_human()}][ignore-sigterm] SIGTERM, ignoring...`);
|
|
710
|
-
});
|
|
711
|
-
process.on('SIGINT', function () {
|
|
712
|
-
console.log(`[${now_human()}][ignore-sigterm] SIGINT, ignoring...`);
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
for (let iter = 1, last = 100; iter <= last; ++iter) {
|
|
716
|
-
console.log(`[${now_human()}][ignore-sigterm] ${iter} of ${last}`);
|
|
717
|
-
await Promise.delay(100);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
/***/ }),
|
|
723
|
-
|
|
724
|
-
/***/ "./src/cli.js":
|
|
725
|
-
/*!********************!*\
|
|
726
|
-
!*** ./src/cli.js ***!
|
|
727
|
-
\********************/
|
|
728
|
-
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
729
|
-
|
|
730
|
-
const ExitCodeError = __webpack_require__(/*! ./errors/ExitCodeError */ "./src/errors/ExitCodeError.js");
|
|
731
|
-
const Promise = __webpack_require__(/*! bluebird */ "bluebird");
|
|
728
|
+
/***/ "./src/clamp.js":
|
|
729
|
+
/*!**********************!*\
|
|
730
|
+
!*** ./src/clamp.js ***!
|
|
731
|
+
\**********************/
|
|
732
|
+
/***/ ((module) => {
|
|
732
733
|
|
|
733
|
-
|
|
734
|
-
* Entry point for Node CLI apps
|
|
735
|
-
*/
|
|
736
|
-
function cli(main, report = error => console.error(error))
|
|
734
|
+
function clamp(min, max, value)
|
|
737
735
|
{
|
|
738
|
-
|
|
739
|
-
const timer = setInterval(v => v, 1E9);
|
|
740
|
-
|
|
741
|
-
Promise.resolve(main()).then(resolve, reject);
|
|
742
|
-
|
|
743
|
-
function resolve() {
|
|
744
|
-
clearInterval(timer);
|
|
745
|
-
}
|
|
746
|
-
function reject(error) {
|
|
747
|
-
clearInterval(timer);
|
|
748
|
-
report(error);
|
|
749
|
-
if (error instanceof ExitCodeError) {
|
|
750
|
-
process.exit(error.exit_code);
|
|
751
|
-
}
|
|
752
|
-
else {
|
|
753
|
-
process.exit(1);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
736
|
+
return Math.max(min, Math.min(max, value));
|
|
756
737
|
}
|
|
757
738
|
|
|
758
|
-
module.exports =
|
|
739
|
+
module.exports = clamp;
|
|
759
740
|
|
|
760
741
|
|
|
761
742
|
/***/ }),
|
|
@@ -901,25 +882,6 @@ function date_is_leap_year(d)
|
|
|
901
882
|
module.exports = date_is_leap_year;
|
|
902
883
|
|
|
903
884
|
|
|
904
|
-
/***/ }),
|
|
905
|
-
|
|
906
|
-
/***/ "./src/errors/ExitCodeError.js":
|
|
907
|
-
/*!*************************************!*\
|
|
908
|
-
!*** ./src/errors/ExitCodeError.js ***!
|
|
909
|
-
\*************************************/
|
|
910
|
-
/***/ ((module) => {
|
|
911
|
-
|
|
912
|
-
class ExitCodeError extends Error
|
|
913
|
-
{
|
|
914
|
-
constructor(exit_code, message = '') {
|
|
915
|
-
super(message);
|
|
916
|
-
this.exit_code = exit_code;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
module.exports = ExitCodeError;
|
|
921
|
-
|
|
922
|
-
|
|
923
885
|
/***/ }),
|
|
924
886
|
|
|
925
887
|
/***/ "./src/factorize_ms.js":
|
|
@@ -1725,7 +1687,7 @@ const factorize_ms = __webpack_require__(/*! ./factorize_ms */ "./src/factorize_
|
|
|
1725
1687
|
/**
|
|
1726
1688
|
* Format milliseconds
|
|
1727
1689
|
*/
|
|
1728
|
-
function format_ms3(ms)
|
|
1690
|
+
function format_ms3(ms, include_zero_hours = false)
|
|
1729
1691
|
{
|
|
1730
1692
|
if (!ms) {
|
|
1731
1693
|
ms = 0;
|
|
@@ -1733,7 +1695,7 @@ function format_ms3(ms)
|
|
|
1733
1695
|
|
|
1734
1696
|
// 00:05.445
|
|
1735
1697
|
const [h, m, s, xx] = factorize_ms(ms);
|
|
1736
|
-
if (h) {
|
|
1698
|
+
if (h || include_zero_hours) {
|
|
1737
1699
|
return `0${h}:0${m}:0${s}.00${xx}`.replace(/0+(?=\d\d+[:.]|\d\d\d)/g, '');
|
|
1738
1700
|
}
|
|
1739
1701
|
return `0${m}:0${s}.00${xx}`.replace(/0+(?=\d\d+[:.]|\d\d\d)/g, '')
|
|
@@ -2219,99 +2181,6 @@ function math_lcm(a, b)
|
|
|
2219
2181
|
module.exports = math_lcm;
|
|
2220
2182
|
|
|
2221
2183
|
|
|
2222
|
-
/***/ }),
|
|
2223
|
-
|
|
2224
|
-
/***/ "./src/now_human.js":
|
|
2225
|
-
/*!**************************!*\
|
|
2226
|
-
!*** ./src/now_human.js ***!
|
|
2227
|
-
\**************************/
|
|
2228
|
-
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2229
|
-
|
|
2230
|
-
const format_date_human = __webpack_require__(/*! ./format_date_human */ "./src/format_date_human.js");
|
|
2231
|
-
|
|
2232
|
-
function now_human()
|
|
2233
|
-
{
|
|
2234
|
-
return format_date_human(new Date());
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
module.exports = now_human;
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
/***/ }),
|
|
2241
|
-
|
|
2242
|
-
/***/ "./src/pid_kill_grace.d/ignore-sigterm-for-10ms.js":
|
|
2243
|
-
/*!*********************************************************!*\
|
|
2244
|
-
!*** ./src/pid_kill_grace.d/ignore-sigterm-for-10ms.js ***!
|
|
2245
|
-
\*********************************************************/
|
|
2246
|
-
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
|
|
2247
|
-
|
|
2248
|
-
//#!/usr/bin/env node
|
|
2249
|
-
|
|
2250
|
-
const Promise = __webpack_require__(/*! bluebird */ "bluebird");
|
|
2251
|
-
const cli = __webpack_require__(/*! ../cli */ "./src/cli.js");
|
|
2252
|
-
const now_human = __webpack_require__(/*! ../now_human */ "./src/now_human.js");
|
|
2253
|
-
|
|
2254
|
-
// Child ignores SIGTERM for 10 ms, but dies naturally before the grace loop ends
|
|
2255
|
-
|
|
2256
|
-
cli(main);
|
|
2257
|
-
|
|
2258
|
-
async function main()
|
|
2259
|
-
{
|
|
2260
|
-
let timer;
|
|
2261
|
-
|
|
2262
|
-
process.on('SIGTERM', function () {
|
|
2263
|
-
console.log(`[${now_human()}][ignore-sigterm-for-10ms] SIGTERM_ignoring...`);
|
|
2264
|
-
timer ??= setTimeout(terminate, 10);
|
|
2265
|
-
});
|
|
2266
|
-
process.on('SIGINT', function () {
|
|
2267
|
-
console.log(`[${now_human()}][ignore-sigterm-for-10ms] SIGINT_ignoring...`);
|
|
2268
|
-
});
|
|
2269
|
-
|
|
2270
|
-
for (let iter = 1, last = 100; iter <= last; ++iter) {
|
|
2271
|
-
console.log(`[${now_human()}][ignore-sigterm-for-10ms] ${iter} of ${last}`);
|
|
2272
|
-
await Promise.delay(10);
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
function terminate()
|
|
2277
|
-
{
|
|
2278
|
-
console.log(`[${now_human()}][ignore-sigterm-for-10ms] TERMINATE_AFTER_10MS`);
|
|
2279
|
-
process.exit(0);
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
/***/ }),
|
|
2284
|
-
|
|
2285
|
-
/***/ "./src/pid_kill_grace.d/ignore-sigterm.js":
|
|
2286
|
-
/*!************************************************!*\
|
|
2287
|
-
!*** ./src/pid_kill_grace.d/ignore-sigterm.js ***!
|
|
2288
|
-
\************************************************/
|
|
2289
|
-
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
|
|
2290
|
-
|
|
2291
|
-
//#!/usr/bin/env node
|
|
2292
|
-
|
|
2293
|
-
const Promise = __webpack_require__(/*! bluebird */ "bluebird");
|
|
2294
|
-
const cli = __webpack_require__(/*! ../cli */ "./src/cli.js");
|
|
2295
|
-
const now_human = __webpack_require__(/*! ../now_human */ "./src/now_human.js");
|
|
2296
|
-
|
|
2297
|
-
cli(main);
|
|
2298
|
-
|
|
2299
|
-
async function main()
|
|
2300
|
-
{
|
|
2301
|
-
process.on('SIGTERM', function () {
|
|
2302
|
-
console.log(`[${now_human()}][ignore-sigterm] SIGTERM_ignoring...`);
|
|
2303
|
-
});
|
|
2304
|
-
process.on('SIGINT', function () {
|
|
2305
|
-
console.log(`[${now_human()}][ignore-sigterm] SIGINT_ignoring...`);
|
|
2306
|
-
});
|
|
2307
|
-
|
|
2308
|
-
for (let iter = 1, last = 100; iter <= last; ++iter) {
|
|
2309
|
-
console.log(`[${now_human()}][ignore-sigterm] ${iter} of ${last}`);
|
|
2310
|
-
await Promise.delay(10);
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
2184
|
/***/ }),
|
|
2316
2185
|
|
|
2317
2186
|
/***/ "./src/plural.js":
|
|
@@ -2350,6 +2219,117 @@ function random_int(min, max)
|
|
|
2350
2219
|
module.exports = random_int;
|
|
2351
2220
|
|
|
2352
2221
|
|
|
2222
|
+
/***/ }),
|
|
2223
|
+
|
|
2224
|
+
/***/ "./src/round.js":
|
|
2225
|
+
/*!**********************!*\
|
|
2226
|
+
!*** ./src/round.js ***!
|
|
2227
|
+
\**********************/
|
|
2228
|
+
/***/ ((module) => {
|
|
2229
|
+
|
|
2230
|
+
function round(value, precision = 1)
|
|
2231
|
+
{
|
|
2232
|
+
if (precision === 0) {
|
|
2233
|
+
return value;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
const factor = 1 / precision;
|
|
2237
|
+
const sign = Math.sign(value);
|
|
2238
|
+
|
|
2239
|
+
// Count decimal places for the cleanup (toFixed)
|
|
2240
|
+
const exponent = get_decimal_places(precision);
|
|
2241
|
+
|
|
2242
|
+
// Small epsilon helps fix most floating point edge cases involving ties
|
|
2243
|
+
const epsilon = 1e-12*sign;
|
|
2244
|
+
|
|
2245
|
+
// Scale → Nudge → Round → Unscale
|
|
2246
|
+
const final = (Math.round(value*factor + epsilon) / factor);
|
|
2247
|
+
|
|
2248
|
+
// Cleanup trailing garbage such as: 34.038000000000004 → 34.038
|
|
2249
|
+
return Number(final.toFixed(exponent));
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
function get_decimal_places(precision)
|
|
2253
|
+
{
|
|
2254
|
+
if (Math.floor(precision) === precision) {
|
|
2255
|
+
return 0;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
// Scientific notation support: e.g., 1e-20
|
|
2259
|
+
const e = precision.toExponential().split('e');
|
|
2260
|
+
if (e.length > 1) {
|
|
2261
|
+
return Math.abs(parseInt(e[1], 10));
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
// Normal decimal notation (e.g., 0.001)
|
|
2265
|
+
return precision.toString().split('.')[1]?.length || 0;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// // round(34.037531, 0.001) -> 34.038000000000004
|
|
2269
|
+
// function round(value, precision = 1)
|
|
2270
|
+
// {
|
|
2271
|
+
// return Math.round(value / precision) * precision;
|
|
2272
|
+
// }
|
|
2273
|
+
|
|
2274
|
+
// // round(1.005, 0.01) -> 1
|
|
2275
|
+
// function round(value, precision = 1)
|
|
2276
|
+
// {
|
|
2277
|
+
// const p = 1 / precision;
|
|
2278
|
+
// return Math.round(value * p) / p;
|
|
2279
|
+
// }
|
|
2280
|
+
|
|
2281
|
+
// function round(value, precision = 1)
|
|
2282
|
+
// {
|
|
2283
|
+
// if (precision === 0) {
|
|
2284
|
+
// return value;
|
|
2285
|
+
// }
|
|
2286
|
+
// const factor = 1 / precision;
|
|
2287
|
+
// const epsilon = 1e-12 * Math.sign(value);
|
|
2288
|
+
// return Number((Math.round((value * factor) + epsilon) / factor).toPrecision(15));
|
|
2289
|
+
// }
|
|
2290
|
+
|
|
2291
|
+
// function round(value, precision = 1)
|
|
2292
|
+
// {
|
|
2293
|
+
// if (precision === 0) {
|
|
2294
|
+
// return value;
|
|
2295
|
+
// }
|
|
2296
|
+
//
|
|
2297
|
+
// const factor = 1 / precision;
|
|
2298
|
+
//
|
|
2299
|
+
// // The exponent is critical for defining the precision of the cleanup
|
|
2300
|
+
// const exponent = Math.max(0, String(precision).length - 2);
|
|
2301
|
+
//
|
|
2302
|
+
// // 1. Calculate the scaled value.
|
|
2303
|
+
// let scaled = value * factor;
|
|
2304
|
+
//
|
|
2305
|
+
// // 2. Strategic Nudge: Add a tiny, signed epsilon (1e-12) to correct floating-point
|
|
2306
|
+
// // representation errors and force symmetric rounding (as Math.abs failed).
|
|
2307
|
+
// const epsilon = 1e-12 * Math.sign(value);
|
|
2308
|
+
//
|
|
2309
|
+
// // 3. Round the nudged value.
|
|
2310
|
+
// const roundedScaled = Math.round(scaled + epsilon);
|
|
2311
|
+
//
|
|
2312
|
+
// // 4. Unscale the value.
|
|
2313
|
+
// const result = roundedScaled / factor;
|
|
2314
|
+
//
|
|
2315
|
+
// // 5. CRITICAL FINAL STEP: Use .toFixed() with the required decimal places for cleanup.
|
|
2316
|
+
// // This stabilizes the result against the strict equality checks, including
|
|
2317
|
+
// // the safe integer boundary errors caused by the final division.
|
|
2318
|
+
// return Number(result.toFixed(exponent));
|
|
2319
|
+
// }
|
|
2320
|
+
|
|
2321
|
+
// 💎 The most precise solution, but - extra dependency.
|
|
2322
|
+
//
|
|
2323
|
+
// const Decimal = require('decimal.js');
|
|
2324
|
+
//
|
|
2325
|
+
// function round(value, precision = 1)
|
|
2326
|
+
// {
|
|
2327
|
+
// return new Decimal(value).div(precision).round().times(precision).toNumber();
|
|
2328
|
+
// }
|
|
2329
|
+
|
|
2330
|
+
module.exports = round;
|
|
2331
|
+
|
|
2332
|
+
|
|
2353
2333
|
/***/ }),
|
|
2354
2334
|
|
|
2355
2335
|
/***/ "./src/urlmod.js":
|
|
@@ -2499,14 +2479,14 @@ var __webpack_exports__ = {};
|
|
|
2499
2479
|
\******************************/
|
|
2500
2480
|
const ns = new URL(document.currentScript.src).searchParams.get('var') ?? 'h';
|
|
2501
2481
|
if (typeof window[ns] !== 'undefined') {
|
|
2502
|
-
console.log(`❌ @vbarbarosh/node-helpers@${"3.
|
|
2482
|
+
console.log(`❌ @vbarbarosh/node-helpers@${"3.73.0"} was not injected — window.${ns} is already in use`);
|
|
2503
2483
|
}
|
|
2504
2484
|
else {
|
|
2505
|
-
console.log(`🎉 @vbarbarosh/node-helpers@${"3.
|
|
2485
|
+
console.log(`🎉 @vbarbarosh/node-helpers@${"3.73.0"} successfully exposed as window.${ns}`);
|
|
2506
2486
|
window[ns] = {};
|
|
2507
2487
|
// https://github.com/webpack/webpack/issues/625
|
|
2508
2488
|
// https://webpack.js.org/guides/dependency-management/#require-context
|
|
2509
|
-
const require_tmp = __webpack_require__("./src sync recursive
|
|
2489
|
+
const require_tmp = __webpack_require__("./src sync recursive ^(?%21.*(cli-apps%7Cbrowser%7C\\.d)\\/).*\\b(array%7Cdate%7Chttp_delete%7Chttp_get_blob%7Chttp_get_buffer%7Chttp_get_json%7Chttp_get_utf8%7Chttp_head%7Chttp_patch_json%7Chttp_post_json%7Chttp_post_multipart%7Chttp_put_buffer%7Chttp_put_json%7Chttp_put_utf8%7Cidentity%7Cignore%7Cfcmp%7Cfilter%7Cformat%7Cplural%7Crandom_int%7Curlmod%7Cwaitcb)[^/]*(?<%21\\.test)\\.js$");
|
|
2510
2490
|
require_tmp.keys().forEach(function (key) {
|
|
2511
2491
|
const [, basename] = key.match(/([^/]+)\.js$/);
|
|
2512
2492
|
window[ns][basename] = require_tmp(key);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "Vladimir Barbarosh <vladimir.barbarosh@gmail.com>",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@vbarbarosh/node-helpers",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.73.0",
|
|
6
6
|
"description": "A set of helpers for JavaScript/Node.js",
|
|
7
7
|
"unpkg": "dist/browser.js",
|
|
8
8
|
"jsdelivr": "dist/browser.js",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const clamp = require('./clamp');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Split an array of jobs between up to `max_threads`, while trying to put at least
|
|
5
|
+
* `min_jobs_per_thread` items into each chunk.
|
|
6
|
+
*
|
|
7
|
+
* - If an array is empty → returns [].
|
|
8
|
+
* - If there are too few items to satisfy `min_items_per_chunk`, even for 1 chunk,
|
|
9
|
+
* everything goes into a single chunk.
|
|
10
|
+
*
|
|
11
|
+
* Alternative names:
|
|
12
|
+
* - array_chunk_balanced
|
|
13
|
+
*
|
|
14
|
+
* @param array
|
|
15
|
+
* @param max_threads
|
|
16
|
+
* @param min_jobs_per_thread
|
|
17
|
+
* @returns {*[]}
|
|
18
|
+
*/
|
|
19
|
+
function array_chunk_jobs(array = [], max_threads = 1, min_jobs_per_thread = 1)
|
|
20
|
+
{
|
|
21
|
+
const total_threads = clamp(1, max_threads, Math.floor(array.length / min_jobs_per_thread));
|
|
22
|
+
const jobs_per_thread = Math.floor(array.length / total_threads);
|
|
23
|
+
let extra = array.length % total_threads;
|
|
24
|
+
|
|
25
|
+
const out = [];
|
|
26
|
+
for (let i = 0; i < array.length; ) {
|
|
27
|
+
const size = jobs_per_thread + (extra ? 1 : 0);
|
|
28
|
+
out.push(array.slice(i, i + size));
|
|
29
|
+
i += size;
|
|
30
|
+
if (extra) {
|
|
31
|
+
extra--;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = array_chunk_jobs;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const array_chunk_jobs = require('./array_chunk_jobs');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('array_chunk_jobs', function () {
|
|
5
|
+
it('should handle basic input', function () {
|
|
6
|
+
assert.deepStrictEqual(array_chunk_jobs([1]), [[1]]);
|
|
7
|
+
assert.deepStrictEqual(array_chunk_jobs([1,2]), [[1,2]]);
|
|
8
|
+
assert.deepStrictEqual(array_chunk_jobs([1,2,3]), [[1,2,3]]);
|
|
9
|
+
assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5], 3, 2), [[1,2,3],[4,5]]);
|
|
10
|
+
assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5,6], 3, 2), [[1,2],[3,4],[5,6]]);
|
|
11
|
+
assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5,6,7], 3, 2), [[1,2,3],[4,5],[6,7]]);
|
|
12
|
+
assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5], 3, 3), [[1,2,3,4,5]]);
|
|
13
|
+
assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5,6], 3, 3), [[1,2,3],[4,5,6]]);
|
|
14
|
+
assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5,6,7], 3, 3), [[1,2,3,4],[5,6,7]]);
|
|
15
|
+
assert.deepStrictEqual(array_chunk_jobs([1,2,3,4,5,6,7,8], 3, 3), [[1,2,3,4],[5,6,7,8]]);
|
|
16
|
+
});
|
|
17
|
+
it('edge: If an array is empty → returns []', function () {
|
|
18
|
+
assert.deepStrictEqual(array_chunk_jobs([]), []);
|
|
19
|
+
});
|
|
20
|
+
it('edge: If there are too few items to satisfy `min_items_per_chunk`, even for 1 chunk, everything goes into a single chunk', function () {
|
|
21
|
+
assert.deepStrictEqual(array_chunk_jobs([1,2,3], 5, 5), [[1,2,3]]);
|
|
22
|
+
});
|
|
23
|
+
it('should never produce more chunks than max_threads', function () {
|
|
24
|
+
// 12 jobs, max 5 threads, min 2 jobs per thread
|
|
25
|
+
// Correct result MUST have exactly 5 chunks
|
|
26
|
+
assert.deepStrictEqual(
|
|
27
|
+
array_chunk_jobs([1,2,3,4,5,6,7,8,9,10,11,12], 5, 2),
|
|
28
|
+
[
|
|
29
|
+
[1,2,3],
|
|
30
|
+
[4,5,6],
|
|
31
|
+
[7,8],
|
|
32
|
+
[9,10],
|
|
33
|
+
[11,12]
|
|
34
|
+
]
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
});
|
package/src/array_fps.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const round = require('./round');
|
|
2
|
+
|
|
3
|
+
const EPS = 0.00001;
|
|
4
|
+
|
|
5
|
+
function array_fps(duration_sec, fps)
|
|
6
|
+
{
|
|
7
|
+
const total_frames = Math.floor(duration_sec * fps);
|
|
8
|
+
|
|
9
|
+
if (total_frames <= 1) {
|
|
10
|
+
return [0];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const out = [];
|
|
14
|
+
for (let i = 0; i < total_frames; ++i) {
|
|
15
|
+
out.push(round((i / (total_frames - 1)) * (duration_sec - EPS), EPS));
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = array_fps;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const array_fps = require('./array_fps');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('array_fps', function () {
|
|
5
|
+
it('should handle basic input', function () {
|
|
6
|
+
assert.deepStrictEqual(array_fps(1, 1), [0]);
|
|
7
|
+
assert.deepStrictEqual(array_fps(1, 2), [0, 0.99999]);
|
|
8
|
+
assert.deepStrictEqual(array_fps(1, 3), [0, 0.49999, 0.99999]);
|
|
9
|
+
assert.deepStrictEqual(array_fps(1, 4), [0, 0.33333, 0.66666, 0.99999]);
|
|
10
|
+
});
|
|
11
|
+
});
|
package/src/browser/index.js
CHANGED
|
@@ -7,7 +7,7 @@ else {
|
|
|
7
7
|
window[ns] = {};
|
|
8
8
|
// https://github.com/webpack/webpack/issues/625
|
|
9
9
|
// https://webpack.js.org/guides/dependency-management/#require-context
|
|
10
|
-
const require_tmp = require.context('..', true,
|
|
10
|
+
const require_tmp = require.context('..', true, /^(?!.*(cli-apps|browser|\.d)\/).*\b(array|date|http_delete|http_get_blob|http_get_buffer|http_get_json|http_get_utf8|http_head|http_patch_json|http_post_json|http_post_multipart|http_put_buffer|http_put_json|http_put_utf8|identity|ignore|fcmp|filter|format|plural|random_int|urlmod|waitcb)[^/]*(?<!\.test)\.js$/);
|
|
11
11
|
require_tmp.keys().forEach(function (key) {
|
|
12
12
|
const [, basename] = key.match(/([^/]+)\.js$/);
|
|
13
13
|
window[ns][basename] = require_tmp(key);
|
package/src/clamp.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const Promise = require('bluebird');
|
|
4
|
+
const cli = require('../../cli');
|
|
5
|
+
const ping_socket = require('../../ping_socket');
|
|
6
|
+
|
|
7
|
+
cli(main);
|
|
8
|
+
|
|
9
|
+
async function main()
|
|
10
|
+
{
|
|
11
|
+
for (let iter = 1, last = 2; iter <= last; ++iter) {
|
|
12
|
+
await ping_socket(process.env.WATCHDOG_SOCKET);
|
|
13
|
+
await Promise.delay(10);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const Promise = require('bluebird');
|
|
4
|
+
const cli = require('../../cli');
|
|
5
|
+
const ping_socket = require('../../ping_socket');
|
|
6
|
+
|
|
7
|
+
cli(main);
|
|
8
|
+
|
|
9
|
+
async function main()
|
|
10
|
+
{
|
|
11
|
+
await ping_socket(process.env.WATCHDOG_SOCKET);
|
|
12
|
+
while (true) {
|
|
13
|
+
await Promise.delay(1000);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/cli-apps/watchdog.js
CHANGED
|
@@ -15,6 +15,7 @@ const shell_spawn = require('../shell_spawn');
|
|
|
15
15
|
|
|
16
16
|
const STARTED_AT = Date.now();
|
|
17
17
|
const LOGS_ROOT_UID = cuid.createId();
|
|
18
|
+
const ARGS = process.argv.slice(2);
|
|
18
19
|
|
|
19
20
|
let PING_COUNTER = 0;
|
|
20
21
|
|
|
@@ -35,18 +36,17 @@ cli(main, report);
|
|
|
35
36
|
|
|
36
37
|
function report(error)
|
|
37
38
|
{
|
|
38
|
-
log(`[watchdog_end_error] ${render_stats()} | ${error.message}`);
|
|
39
|
+
log(`[watchdog_end_error] ${render_stats()} | ${error.message} | ${ARGS.join(' ')}`);
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
async function main()
|
|
42
43
|
{
|
|
43
|
-
|
|
44
|
-
if (!args.length) {
|
|
44
|
+
if (!ARGS.length) {
|
|
45
45
|
usage();
|
|
46
46
|
process.exit(1);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
switch (
|
|
49
|
+
switch (ARGS[0]) {
|
|
50
50
|
case '-v':
|
|
51
51
|
case '--version':
|
|
52
52
|
version();
|
|
@@ -56,18 +56,20 @@ async function main()
|
|
|
56
56
|
|
|
57
57
|
log(`[watchdog_begin] v${pkg.version}`);
|
|
58
58
|
|
|
59
|
-
const WATCHDOG_INTERVAL =
|
|
59
|
+
const WATCHDOG_INTERVAL = process.env.__TESTING__
|
|
60
|
+
? make(process.env.WATCHDOG_INTERVAL, {type: 'int', min: 1, default: 5000})
|
|
61
|
+
: make(process.env.WATCHDOG_INTERVAL, {type: 'int', min: 1000, default: 5000});
|
|
60
62
|
const heartbeat_server = new HeartbeatServer(WATCHDOG_INTERVAL);
|
|
61
63
|
const WATCHDOG_SOCKET = heartbeat_server.socket_path;
|
|
62
64
|
heartbeat_server.on('heartbeat', () => log(`[watchdog_ping] ❤️ ${render_stats(++PING_COUNTER)}`))
|
|
63
65
|
heartbeat_server.on('warning', error => log(`[watchdog_warn] ⚠️ ${error}`))
|
|
64
66
|
|
|
65
67
|
try {
|
|
66
|
-
log(`[watchdog_args] ${
|
|
68
|
+
log(`[watchdog_args] ${ARGS.join(' ')}`);
|
|
67
69
|
log(`[watchdog_interval] ${WATCHDOG_INTERVAL}`);
|
|
68
70
|
log(`[watchdog_socket_path] ${heartbeat_server.socket_path}`);
|
|
69
71
|
const env = {...process.env, WATCHDOG_INTERVAL, WATCHDOG_SOCKET};
|
|
70
|
-
const proc = await shell_spawn(
|
|
72
|
+
const proc = await shell_spawn(ARGS, {stdio: 'inherit', env, detached: true}).init();
|
|
71
73
|
const pgid = proc.pid; // group id == leader pid
|
|
72
74
|
|
|
73
75
|
const signal = new Promise(function (resolve, reject) {
|
|
@@ -98,7 +100,7 @@ async function main()
|
|
|
98
100
|
await Promise.resolve(heartbeat_server.dispose()).timeout(1000);
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
log(`[watchdog_end_ok] ${render_stats()}`);
|
|
103
|
+
log(`[watchdog_end_ok] ${render_stats()} | ${ARGS.join(' ')}`);
|
|
102
104
|
process.exit(0);
|
|
103
105
|
}
|
|
104
106
|
|
|
@@ -106,10 +108,10 @@ function log(s)
|
|
|
106
108
|
{
|
|
107
109
|
const ss = JSON.stringify(s).slice(1, -1).replaceAll('\\"', '"');
|
|
108
110
|
if (ss[0] === '[') {
|
|
109
|
-
console.log(`[${LOGS_ROOT_UID}][
|
|
111
|
+
console.log(`[${LOGS_ROOT_UID}][${format_ms3(Date.now() - STARTED_AT, true)}]${ss}`);
|
|
110
112
|
}
|
|
111
113
|
else {
|
|
112
|
-
console.log(`[${LOGS_ROOT_UID}][
|
|
114
|
+
console.log(`[${LOGS_ROOT_UID}][${format_ms3(Date.now() - STARTED_AT, true)}] ${ss}`);
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
|
|
@@ -1,7 +1,48 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
|
+
const pid_exists = require('../pid_exists');
|
|
3
|
+
const shell_spawn = require('../shell_spawn');
|
|
4
|
+
const stream = require('stream');
|
|
5
|
+
const stream_each = require('../stream_each');
|
|
6
|
+
const stream_lines = require('../stream_lines');
|
|
2
7
|
|
|
3
8
|
// watchdog true -> race condition; true exists too quickly; bug in shell_spawn
|
|
4
9
|
|
|
5
|
-
describe
|
|
6
|
-
|
|
10
|
+
describe('watchdog', function () {
|
|
11
|
+
this.timeout(1000);
|
|
12
|
+
it('Happy Path', async function () {
|
|
13
|
+
const env = {...process.env, WATCHDOG_INTERVAL: 100, __TESTING__: 1};
|
|
14
|
+
const proc = shell_spawn([`${__dirname}/watchdog.js`, `${__dirname}/watchdog.d/quick-exit-after-2-pings.js`], {env});
|
|
15
|
+
try {
|
|
16
|
+
const stdout = [];
|
|
17
|
+
await Promise.all([
|
|
18
|
+
stream.promises.pipeline(proc.stdout, stream_lines(), stream_each(v => stdout.push(v))),
|
|
19
|
+
proc.promise(),
|
|
20
|
+
]);
|
|
21
|
+
assert.strictEqual(stdout.filter(v => v.includes('[watchdog_ping]')).length, 2);
|
|
22
|
+
assert.strictEqual(stdout.filter(v => v.includes('[watchdog_end_ok] pings=2')).length, 1);
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
if (pid_exists(proc.pid)) {
|
|
26
|
+
process.kill(proc.pid, 'SIGTERM');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
it('Hang after 1 ping', async function () {
|
|
31
|
+
const env = {...process.env, WATCHDOG_INTERVAL: 100, __TESTING__: 1};
|
|
32
|
+
const proc = shell_spawn([`${__dirname}/watchdog.js`, `${__dirname}/watchdog.d/quick-hang-after-1-ping.js`], {env});
|
|
33
|
+
const stdout = [];
|
|
34
|
+
try {
|
|
35
|
+
await Promise.all([
|
|
36
|
+
stream.promises.pipeline(proc.stdout, stream_lines(), stream_each(v => stdout.push(v))),
|
|
37
|
+
assert.rejects(proc.promise().timeout(500), {message: 'Process terminated with code 124 and signal null'}),
|
|
38
|
+
]);
|
|
39
|
+
assert.strictEqual(stdout.filter(v => v.includes('[watchdog_ping]')).length, 1);
|
|
40
|
+
assert.strictEqual(stdout.filter(v => v.includes('[watchdog_end_error] pings=1 | No heartbeat for the last 100ms')).length, 1);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
if (pid_exists(proc.pid)) {
|
|
44
|
+
process.kill(proc.pid, 'SIGTERM');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
7
48
|
});
|
package/src/format_ms3.js
CHANGED
|
@@ -3,7 +3,7 @@ const factorize_ms = require('./factorize_ms');
|
|
|
3
3
|
/**
|
|
4
4
|
* Format milliseconds
|
|
5
5
|
*/
|
|
6
|
-
function format_ms3(ms)
|
|
6
|
+
function format_ms3(ms, include_zero_hours = false)
|
|
7
7
|
{
|
|
8
8
|
if (!ms) {
|
|
9
9
|
ms = 0;
|
|
@@ -11,7 +11,7 @@ function format_ms3(ms)
|
|
|
11
11
|
|
|
12
12
|
// 00:05.445
|
|
13
13
|
const [h, m, s, xx] = factorize_ms(ms);
|
|
14
|
-
if (h) {
|
|
14
|
+
if (h || include_zero_hours) {
|
|
15
15
|
return `0${h}:0${m}:0${s}.00${xx}`.replace(/0+(?=\d\d+[:.]|\d\d\d)/g, '');
|
|
16
16
|
}
|
|
17
17
|
return `0${m}:0${s}.00${xx}`.replace(/0+(?=\d\d+[:.]|\d\d\d)/g, '')
|
package/src/fs_lsr.js
CHANGED
|
@@ -3,6 +3,9 @@ const fs_fi = require('./fs_fi');
|
|
|
3
3
|
const fs_path_join = require('./fs_path_join');
|
|
4
4
|
const fs_readdir = require('./fs_readdir');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Deprecated in favor or fs_walk
|
|
8
|
+
*/
|
|
6
9
|
async function fs_lsr(pathname = '.')
|
|
7
10
|
{
|
|
8
11
|
const fi = await fs_fi(pathname);
|
package/src/fs_walk.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fs_lstat = require('./fs_lstat');
|
|
2
|
+
const fs_path_join = require('./fs_path_join');
|
|
3
|
+
const fs_readdir = require('./fs_readdir');
|
|
4
|
+
const ignore = require('./ignore');
|
|
5
|
+
|
|
6
|
+
async function fs_walk({path = '.', user_friendly_status = ignore, error_handler = error_handler, signal})
|
|
7
|
+
{
|
|
8
|
+
const out = [];
|
|
9
|
+
const queue = [path];
|
|
10
|
+
while (queue.length) {
|
|
11
|
+
if (signal) {
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const current_path = queue.pop();
|
|
16
|
+
user_friendly_status(`Reading ${current_path}`);
|
|
17
|
+
const lstat = await fs_lstat(current_path);
|
|
18
|
+
lstat.path = current_path;
|
|
19
|
+
out.push(lstat);
|
|
20
|
+
if (lstat.isDirectory()) {
|
|
21
|
+
if (current_path === '/dev' || current_path === '/proc') {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const basenames = await fs_readdir(current_path);
|
|
25
|
+
basenames.forEach(v => queue.push(fs_path_join(current_path, v)));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
error_handler(error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function error_handler(error)
|
|
36
|
+
{
|
|
37
|
+
console.log(`⚠️ ${error.message}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = fs_walk;
|
package/src/round.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
function round(value, precision = 1)
|
|
2
|
+
{
|
|
3
|
+
if (precision === 0) {
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const factor = 1 / precision;
|
|
8
|
+
const sign = Math.sign(value);
|
|
9
|
+
|
|
10
|
+
// Count decimal places for the cleanup (toFixed)
|
|
11
|
+
const exponent = get_decimal_places(precision);
|
|
12
|
+
|
|
13
|
+
// Small epsilon helps fix most floating point edge cases involving ties
|
|
14
|
+
const epsilon = 1e-12*sign;
|
|
15
|
+
|
|
16
|
+
// Scale → Nudge → Round → Unscale
|
|
17
|
+
const final = (Math.round(value*factor + epsilon) / factor);
|
|
18
|
+
|
|
19
|
+
// Cleanup trailing garbage such as: 34.038000000000004 → 34.038
|
|
20
|
+
return Number(final.toFixed(exponent));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function get_decimal_places(precision)
|
|
24
|
+
{
|
|
25
|
+
if (Math.floor(precision) === precision) {
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Scientific notation support: e.g., 1e-20
|
|
30
|
+
const e = precision.toExponential().split('e');
|
|
31
|
+
if (e.length > 1) {
|
|
32
|
+
return Math.abs(parseInt(e[1], 10));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Normal decimal notation (e.g., 0.001)
|
|
36
|
+
return precision.toString().split('.')[1]?.length || 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// // round(34.037531, 0.001) -> 34.038000000000004
|
|
40
|
+
// function round(value, precision = 1)
|
|
41
|
+
// {
|
|
42
|
+
// return Math.round(value / precision) * precision;
|
|
43
|
+
// }
|
|
44
|
+
|
|
45
|
+
// // round(1.005, 0.01) -> 1
|
|
46
|
+
// function round(value, precision = 1)
|
|
47
|
+
// {
|
|
48
|
+
// const p = 1 / precision;
|
|
49
|
+
// return Math.round(value * p) / p;
|
|
50
|
+
// }
|
|
51
|
+
|
|
52
|
+
// function round(value, precision = 1)
|
|
53
|
+
// {
|
|
54
|
+
// if (precision === 0) {
|
|
55
|
+
// return value;
|
|
56
|
+
// }
|
|
57
|
+
// const factor = 1 / precision;
|
|
58
|
+
// const epsilon = 1e-12 * Math.sign(value);
|
|
59
|
+
// return Number((Math.round((value * factor) + epsilon) / factor).toPrecision(15));
|
|
60
|
+
// }
|
|
61
|
+
|
|
62
|
+
// function round(value, precision = 1)
|
|
63
|
+
// {
|
|
64
|
+
// if (precision === 0) {
|
|
65
|
+
// return value;
|
|
66
|
+
// }
|
|
67
|
+
//
|
|
68
|
+
// const factor = 1 / precision;
|
|
69
|
+
//
|
|
70
|
+
// // The exponent is critical for defining the precision of the cleanup
|
|
71
|
+
// const exponent = Math.max(0, String(precision).length - 2);
|
|
72
|
+
//
|
|
73
|
+
// // 1. Calculate the scaled value.
|
|
74
|
+
// let scaled = value * factor;
|
|
75
|
+
//
|
|
76
|
+
// // 2. Strategic Nudge: Add a tiny, signed epsilon (1e-12) to correct floating-point
|
|
77
|
+
// // representation errors and force symmetric rounding (as Math.abs failed).
|
|
78
|
+
// const epsilon = 1e-12 * Math.sign(value);
|
|
79
|
+
//
|
|
80
|
+
// // 3. Round the nudged value.
|
|
81
|
+
// const roundedScaled = Math.round(scaled + epsilon);
|
|
82
|
+
//
|
|
83
|
+
// // 4. Unscale the value.
|
|
84
|
+
// const result = roundedScaled / factor;
|
|
85
|
+
//
|
|
86
|
+
// // 5. CRITICAL FINAL STEP: Use .toFixed() with the required decimal places for cleanup.
|
|
87
|
+
// // This stabilizes the result against the strict equality checks, including
|
|
88
|
+
// // the safe integer boundary errors caused by the final division.
|
|
89
|
+
// return Number(result.toFixed(exponent));
|
|
90
|
+
// }
|
|
91
|
+
|
|
92
|
+
// 💎 The most precise solution, but - extra dependency.
|
|
93
|
+
//
|
|
94
|
+
// const Decimal = require('decimal.js');
|
|
95
|
+
//
|
|
96
|
+
// function round(value, precision = 1)
|
|
97
|
+
// {
|
|
98
|
+
// return new Decimal(value).div(precision).round().times(precision).toNumber();
|
|
99
|
+
// }
|
|
100
|
+
|
|
101
|
+
module.exports = round;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const round = require('./round');
|
|
3
|
+
|
|
4
|
+
const items = [
|
|
5
|
+
[34, 25, 25],
|
|
6
|
+
[37.5, 25, 50],
|
|
7
|
+
[34.037531, 0.001, 34.038],
|
|
8
|
+
[1.005, 0.01, 1.01],
|
|
9
|
+
[0.99999, 0.00001, 0.99999],
|
|
10
|
+
|
|
11
|
+
// Synthetic cases generated by DeepSeek
|
|
12
|
+
[1.005, 0.001, 1.005], // More precise rounding
|
|
13
|
+
[1.005, 0.1, 1.0], // Less precise rounding
|
|
14
|
+
[1.015, 0.01, 1.02], // Another edge case with 5
|
|
15
|
+
[1.025, 0.01, 1.03], // Yet another edge case
|
|
16
|
+
[0.5, 1, 1], // Round to nearest integer
|
|
17
|
+
[0.4, 1, 0], // Round down to integer
|
|
18
|
+
[123.456, 0.01, 123.46], // Standard decimal rounding
|
|
19
|
+
[123.456, 0.1, 123.5], // Different precision
|
|
20
|
+
[123.456, 1, 123], // Round to whole number
|
|
21
|
+
[123.456, 10, 120], // Round to tens
|
|
22
|
+
[123.456, 100, 100], // Round to hundreds
|
|
23
|
+
[0, 0.01, 0], // Zero value
|
|
24
|
+
[-1.005, 0.01, -1.01], // Negative numbers
|
|
25
|
+
[-1.015, 0.01, -1.02], // Negative edge case
|
|
26
|
+
[-37.5, 25, -50], // Negative with integer precision
|
|
27
|
+
[1.2345e-5, 1e-6, 0.000012], // Very small numbers
|
|
28
|
+
[999.999, 0.001, 999.999], // Rounding that causes carry-over
|
|
29
|
+
[0.0000000000001, 0.01, 0], // Extremely small number
|
|
30
|
+
[Number.MAX_SAFE_INTEGER, 1000, 9007199254741000], // Large numbers
|
|
31
|
+
|
|
32
|
+
[0.005, 0.01, 0.01],
|
|
33
|
+
[-1.5, 1, -2], // Symmetric negative rounding (Math.abs is critical here)
|
|
34
|
+
[-0.5, 1, -1], // Symmetric negative rounding for half
|
|
35
|
+
[123456789123.45, 1000, 123456789000], // Testing very large numbers
|
|
36
|
+
[1.2345e-20, 1e-20, 1e-20], // Testing very small numbers
|
|
37
|
+
[1.0000000000000001, 0.0000000000000001, 1.0000000000000000], // Testing rounding near Number.EPSILON range
|
|
38
|
+
|
|
39
|
+
[9007199254740992, 1, 9007199254740992],
|
|
40
|
+
[9007199254740991.5, 1, 9007199254740992],
|
|
41
|
+
[Number.MIN_SAFE_INTEGER, 1, Number.MIN_SAFE_INTEGER],
|
|
42
|
+
[Number.MAX_SAFE_INTEGER, 1, Number.MAX_SAFE_INTEGER],
|
|
43
|
+
|
|
44
|
+
// Out of scope of standard JS
|
|
45
|
+
// [0.004999999999999999, 0.01, 0.0],
|
|
46
|
+
// [Number.MIN_SAFE_INTEGER, 0.1, Number.MIN_SAFE_INTEGER],
|
|
47
|
+
// [Number.MAX_SAFE_INTEGER, 0.1, Number.MAX_SAFE_INTEGER],
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
describe('round', function () {
|
|
51
|
+
items.forEach(function ([value, precision, expected],) {
|
|
52
|
+
it(`${value} [${precision}] → ${expected}`, function () {
|
|
53
|
+
assert.deepStrictEqual(round(value, precision), expected);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
package/src/shell.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Basic helper for running external commands via `execFile`.
|
|
2
|
+
|
|
3
|
+
- Fails if the command exits with non-zero code.
|
|
4
|
+
- Fails if anything is printed to `stderr`.
|
|
5
|
+
- Resolves with `stdout` (string), exactly as produced.
|
|
6
|
+
|
|
7
|
+
## Signature
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
shell(args, options) → Promise<string>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
- `args[0]` is the executable; `args[1..]` are arguments.
|
|
16
|
+
- Suitable for commands that produce clean stdout and no warnings.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Basic helper for running external commands via `execFile`, and parse result by JSON.parse.
|
|
2
|
+
|
|
3
|
+
- Fails if the command exits with non-zero code.
|
|
4
|
+
- Fails if anything is printed to `stderr`.
|
|
5
|
+
- Fails if stdout is not valid JSON.
|
|
6
|
+
- Resolves with parsed JSON value.
|
|
7
|
+
|
|
8
|
+
## Signature
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
shell_json(args, options) → Promise<any>
|
|
12
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Basic helper for running external commands via `execFile`, and splits stdout into lines.
|
|
2
|
+
|
|
3
|
+
- Fails if the command exits with non-zero code.
|
|
4
|
+
- Fails if anything is printed to `stderr`.
|
|
5
|
+
- Strips trailing newline(s).
|
|
6
|
+
- Returns array of lines.
|
|
7
|
+
|
|
8
|
+
## Signature
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
shell_lines(args, options) → Promise<string[]>
|
|
12
|
+
```
|
package/src/shell_spawn.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const ExitCodeError = require('./errors/ExitCodeError');
|
|
2
2
|
const Promise = require('bluebird');
|
|
3
3
|
const child_process = require('child_process');
|
|
4
|
+
const ignore = require('./ignore');
|
|
4
5
|
|
|
5
6
|
// 📕 Notes by ChatGPT:
|
|
6
7
|
// If the child is killed by a signal (e.g. SIGKILL from outside),
|
|
@@ -20,45 +21,44 @@ const child_process = require('child_process');
|
|
|
20
21
|
// > }
|
|
21
22
|
// > }
|
|
22
23
|
|
|
23
|
-
// ⚠️ Both .init and .promise are targets for race conditions!
|
|
24
24
|
function shell_spawn(args, options)
|
|
25
25
|
{
|
|
26
|
-
|
|
26
|
+
const init = {};
|
|
27
|
+
const run = {};
|
|
28
|
+
|
|
29
|
+
init.promise = new Promise(function (resolve, reject) {
|
|
30
|
+
init.resolve = resolve;
|
|
31
|
+
init.reject = reject;
|
|
32
|
+
});
|
|
33
|
+
run.promise = new Promise(function (resolve, reject) {
|
|
34
|
+
run.resolve = resolve;
|
|
35
|
+
run.reject = reject;
|
|
36
|
+
});
|
|
37
|
+
|
|
27
38
|
const out = child_process.spawn(args[0], args.slice(1), options);
|
|
28
|
-
out.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
out.off('spawn', init_spawn);
|
|
34
|
-
reject(error);
|
|
35
|
-
}
|
|
36
|
-
function init_spawn() {
|
|
37
|
-
out.off('error', init_error);
|
|
38
|
-
resolve(out);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
out.promise = function () {
|
|
43
|
-
return promise = promise || new Promise(function (resolve, reject) {
|
|
44
|
-
out.once('error', promise_error);
|
|
45
|
-
out.once('exit', promise_exit);
|
|
46
|
-
function promise_error(error) {
|
|
47
|
-
out.off('exit', promise_exit);
|
|
48
|
-
reject(error);
|
|
49
|
-
}
|
|
50
|
-
function promise_exit(code, signal) {
|
|
51
|
-
out.off('error', promise_error);
|
|
52
|
-
if (code === 0) {
|
|
53
|
-
resolve();
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
reject(new ExitCodeError(code || 128, `Process terminated with code ${code} and signal ${signal}`));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
};
|
|
39
|
+
out.once('error', onerror);
|
|
40
|
+
out.once('spawn', onspawn);
|
|
41
|
+
out.once('exit', onexit);
|
|
42
|
+
out.init = () => init.promise;
|
|
43
|
+
out.promise = () => run.promise;
|
|
61
44
|
return out;
|
|
45
|
+
|
|
46
|
+
function onerror(error) {
|
|
47
|
+
init.reject(error);
|
|
48
|
+
run.reject(error);
|
|
49
|
+
}
|
|
50
|
+
function onexit(code, signal) {
|
|
51
|
+
if (code === 0) {
|
|
52
|
+
run.resolve();
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
run.reject(new ExitCodeError(code || 128, `Process terminated with code ${code} and signal ${signal}`));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function onspawn() {
|
|
59
|
+
init.resolve(out);
|
|
60
|
+
init.reject = ignore;
|
|
61
|
+
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
module.exports = shell_spawn;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Spawn-based helper for long-running or streaming commands.
|
|
2
|
+
|
|
3
|
+
Provides two promises:
|
|
4
|
+
|
|
5
|
+
- `proc.init()` — resolves when the child emits `spawn`.
|
|
6
|
+
- `proc.promise()` — resolves when the child exits with code 0.
|
|
7
|
+
|
|
8
|
+
## Failure semantics
|
|
9
|
+
|
|
10
|
+
- Rejects if `error` event occurs.
|
|
11
|
+
- Rejects if exit code is non-zero.
|
|
12
|
+
- If child dies by signal, treated as non-zero exit (uses exitCode = 128).
|
|
13
|
+
|
|
14
|
+
## Signature
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
const proc = shell_spawn(args, options)
|
|
18
|
+
proc.init() → Promise<ChildProcess>
|
|
19
|
+
proc.promise() → Promise<void>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Notes
|
|
23
|
+
|
|
24
|
+
- Output is streamed (not buffered).
|
|
25
|
+
- Use when you need live stdout/stderr or long-running jobs.
|
package/src/shell_spawn.test.js
CHANGED
|
@@ -3,7 +3,7 @@ const assert = require('assert');
|
|
|
3
3
|
const pid_exists = require('./pid_exists');
|
|
4
4
|
const shell_spawn = require('./shell_spawn');
|
|
5
5
|
|
|
6
|
-
describe
|
|
6
|
+
describe('shell_spawn', function () {
|
|
7
7
|
describe('Happy Path', function () {
|
|
8
8
|
it('basic', async function () {
|
|
9
9
|
await shell_spawn(['true']).promise().timeout(100);
|
|
@@ -20,4 +20,11 @@ describe.skip('shell_spawn', function () {
|
|
|
20
20
|
assert(!pid_exists(proc.pid));
|
|
21
21
|
await proc.promise().timeout(100);
|
|
22
22
|
});
|
|
23
|
+
it('should throw when init fails', async function () {
|
|
24
|
+
await assert.rejects(shell_spawn(['ggg111']).init(), /^Error: spawn ggg111 ENOENT$/);
|
|
25
|
+
});
|
|
26
|
+
it('should throw when process exists with non zero code', async function () {
|
|
27
|
+
const proc = await shell_spawn(['false']).init();
|
|
28
|
+
await assert.rejects(proc.promise().timeout(100), /^Error: Process terminated with code 1 and signal null$/);
|
|
29
|
+
});
|
|
23
30
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Runs a command via `execFile` and returns both stdout and stderr.
|
|
2
|
+
|
|
3
|
+
- Rejects only on exec error.
|
|
4
|
+
- Resolves with `{stdout, stderr}`.
|
|
5
|
+
- Does NOT treat stderr output as failure.
|
|
6
|
+
|
|
7
|
+
## Signature
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
shell_stdall(args, options) → Promise<{stdout: string, stderr: string}>
|
|
11
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Convenience wrapper over `shell_spawn` with `stdio: 'inherit'`.
|
|
2
|
+
|
|
3
|
+
- Child stdout/stderr are streamed directly to parent terminal.
|
|
4
|
+
- Supports `proc.init()` and `proc.promise()` like `shell_spawn`.
|
|
5
|
+
|
|
6
|
+
## Signature
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
shell_thru(args, options) → ChildProcess
|
|
10
|
+
```
|