@ix-xs/node-comfort 1.1.31 → 2.0.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/LICENSE +21 -0
  3. package/README.md +210 -748
  4. package/index.js +84 -13
  5. package/package.json +201 -21
  6. package/src/Arr.js +901 -0
  7. package/src/Async.js +585 -0
  8. package/src/Cache.js +329 -0
  9. package/src/Checker.js +979 -188
  10. package/src/Cli.js +1034 -0
  11. package/src/Color.js +415 -0
  12. package/src/Crypto.js +707 -0
  13. package/src/Emitter.js +289 -0
  14. package/src/Env.js +555 -0
  15. package/src/FS.js +1483 -768
  16. package/src/Func.js +692 -0
  17. package/src/Http.js +538 -0
  18. package/src/Id.js +418 -0
  19. package/src/Logger.js +739 -307
  20. package/src/Num.js +849 -0
  21. package/src/Obj.js +904 -0
  22. package/src/SQLite.js +1308 -994
  23. package/src/Schema.js +1586 -0
  24. package/src/Str.js +999 -0
  25. package/src/Sys.js +511 -0
  26. package/src/Time.js +1323 -0
  27. package/src/Utils.js +321 -325
  28. package/src/errors.js +292 -0
  29. package/types/index.d.ts +693 -120
  30. package/types/src/Arr.d.ts +562 -0
  31. package/types/src/Arr.d.ts.map +1 -0
  32. package/types/src/Async.d.ts +321 -0
  33. package/types/src/Async.d.ts.map +1 -0
  34. package/types/src/Cache.d.ts +195 -0
  35. package/types/src/Cache.d.ts.map +1 -0
  36. package/types/src/Checker.d.ts +609 -81
  37. package/types/src/Checker.d.ts.map +1 -1
  38. package/types/src/Cli.d.ts +582 -0
  39. package/types/src/Cli.d.ts.map +1 -0
  40. package/types/src/Color.d.ts +294 -0
  41. package/types/src/Color.d.ts.map +1 -0
  42. package/types/src/Crypto.d.ts +534 -0
  43. package/types/src/Crypto.d.ts.map +1 -0
  44. package/types/src/Emitter.d.ts +170 -0
  45. package/types/src/Emitter.d.ts.map +1 -0
  46. package/types/src/Env.d.ts +382 -0
  47. package/types/src/Env.d.ts.map +1 -0
  48. package/types/src/FS.d.ts +709 -201
  49. package/types/src/FS.d.ts.map +1 -1
  50. package/types/src/Func.d.ts +455 -0
  51. package/types/src/Func.d.ts.map +1 -0
  52. package/types/src/Http.d.ts +374 -0
  53. package/types/src/Http.d.ts.map +1 -0
  54. package/types/src/Id.d.ts +200 -0
  55. package/types/src/Id.d.ts.map +1 -0
  56. package/types/src/Logger.d.ts +374 -24
  57. package/types/src/Logger.d.ts.map +1 -1
  58. package/types/src/Num.d.ts +578 -0
  59. package/types/src/Num.d.ts.map +1 -0
  60. package/types/src/Obj.d.ts +475 -0
  61. package/types/src/Obj.d.ts.map +1 -0
  62. package/types/src/SQLite.d.ts +647 -305
  63. package/types/src/SQLite.d.ts.map +1 -1
  64. package/types/src/Schema.d.ts +940 -0
  65. package/types/src/Schema.d.ts.map +1 -0
  66. package/types/src/Str.d.ts +696 -0
  67. package/types/src/Str.d.ts.map +1 -0
  68. package/types/src/Sys.d.ts +269 -0
  69. package/types/src/Sys.d.ts.map +1 -0
  70. package/types/src/Time.d.ts +664 -0
  71. package/types/src/Time.d.ts.map +1 -0
  72. package/types/src/Utils.d.ts +154 -102
  73. package/types/src/Utils.d.ts.map +1 -1
  74. package/types/src/errors.d.ts +292 -0
  75. package/types/src/errors.d.ts.map +1 -0
  76. package/tsconfig.json +0 -18
  77. package/types/index.d.ts.map +0 -1
package/src/Sys.js ADDED
@@ -0,0 +1,511 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Processes and the system: run commands and get their output, find
5
+ * executables, shut down cleanly, open URLs, and learn about the machine.
6
+ * Behaves the same on Windows, macOS and Linux; on Windows,
7
+ * `run("npm", [...])` finds `npm.cmd` and escapes arguments safely.
8
+ *
9
+ * @example
10
+ * const { stdout } = await sys.run("git", ["rev-parse", "HEAD"]);
11
+ * sys.onShutdown(async () => {
12
+ * await server.close();
13
+ * await db.close();
14
+ * });
15
+ */
16
+
17
+ const { spawn } = require("node:child_process");
18
+ const fs = require("node:fs");
19
+ const os = require("node:os");
20
+ const nodePath = require("node:path");
21
+ const { ProcessError, AbortError } = require("./errors.js");
22
+
23
+ /**
24
+ * Options for `run()` and `exec()`.
25
+ * @typedef {object} RunOptions
26
+ * @property {string} [cwd] Where to run the command.
27
+ * @property {Record<string, string | undefined>} [env] Extra environment variables, added to `process.env`.
28
+ * @property {boolean} [extendEnv] Set to `false` to pass only `env`, without `process.env`.
29
+ * @property {string | Buffer} [input] Written to the command's stdin.
30
+ * @property {number | string} [timeout] Kill the command after this long, like `"30s"`.
31
+ * @property {AbortSignal} [signal] Kills the command when aborted.
32
+ * @property {"pipe" | "inherit"} [stdio] `"inherit"` shows the output live instead of capturing it. Defaults to `"pipe"`.
33
+ * @property {(line: string) => void} [onStdout] Called for each line of stdout.
34
+ * @property {(line: string) => void} [onStderr] Called for each line of stderr.
35
+ * @property {boolean} [reject] Throw when the exit code isn't 0. Defaults to `true`.
36
+ * @property {boolean} [trim] Drop the final newline of the output. Defaults to `true`.
37
+ */
38
+
39
+ /**
40
+ * A finished command.
41
+ * @typedef {object} RunResult
42
+ * @property {string} command The command line that ran.
43
+ * @property {string} stdout
44
+ * @property {string} stderr
45
+ * @property {number | null} exitCode `null` if a signal killed it.
46
+ * @property {string | null} signal
47
+ * @property {number} duration In milliseconds.
48
+ */
49
+
50
+ /**
51
+ * Options for `onShutdown()`. They apply to all handlers.
52
+ * @typedef {object} ShutdownOptions
53
+ * @property {number | string} [timeout] How long the handlers get before the exit is forced. Defaults to 10 seconds.
54
+ * @property {number} [exitCode] Exit code after a clean shutdown. Defaults to `0`.
55
+ * @property {NodeJS.Signals[]} [signals] Signals that trigger it. Defaults to `SIGINT`, `SIGTERM` and `SIGHUP`.
56
+ */
57
+
58
+ /**
59
+ * Returned by `info()`.
60
+ * @typedef {object} SystemInfo
61
+ * @property {NodeJS.Platform} platform `"win32"`, `"darwin"`, `"linux"`...
62
+ * @property {string} arch `"x64"`, `"arm64"`...
63
+ * @property {string} release OS release.
64
+ * @property {string} node Node.js version.
65
+ * @property {number} cpus Number of logical CPUs.
66
+ * @property {string} cpuModel
67
+ * @property {{ total: number, free: number, used: number }} memory System memory, in bytes.
68
+ * @property {number} uptime In seconds.
69
+ * @property {string} hostname
70
+ * @property {string} user
71
+ * @property {number} pid
72
+ * @property {boolean} ci Running in CI.
73
+ * @property {boolean} docker Running in a container.
74
+ * @property {boolean} wsl Running in WSL.
75
+ */
76
+
77
+ const META = /([()\][%!^"`<>&|;, *?])/g;
78
+
79
+ /** @param {string} command */
80
+ const _escapeCommand = (command) => command.replace(META, "^$1");
81
+
82
+ /**
83
+ * Escapes an argument for cmd.exe, like `cross-spawn` does.
84
+ * @param {string} arg
85
+ * @param {boolean} doubleEscape
86
+ */
87
+ const _escapeArg = (arg, doubleEscape) => {
88
+ let out = String(arg).replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1");
89
+ out = `"${out}"`.replace(META, "^$1");
90
+ return doubleEscape ? out.replace(META, "^$1") : out;
91
+ };
92
+
93
+ /**
94
+ * Handles Windows `.cmd` and `.bat` files.
95
+ * @param {string} file
96
+ * @param {string[]} args
97
+ * @returns {{ file: string, args: string[], verbatim: boolean }}
98
+ */
99
+ const _prepare = (file, args) => {
100
+ if (process.platform !== "win32") return { file, args, verbatim: false };
101
+ const resolved = which(file) ?? file;
102
+ if (!/\.(cmd|bat)$/i.test(resolved)) return { file: resolved, args, verbatim: false };
103
+ const shim = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i.test(resolved);
104
+ const line = [_escapeCommand(nodePath.normalize(resolved)), ...args.map((a) => _escapeArg(a, shim))].join(" ");
105
+ return { file: process.env.comspec || "cmd.exe", args: ["/d", "/s", "/c", `"${line}"`], verbatim: true };
106
+ };
107
+
108
+ /**
109
+ * @param {string} file
110
+ * @param {string[]} args
111
+ * @param {boolean} shell
112
+ * @param {string} display
113
+ * @param {RunOptions} options
114
+ * @returns {Promise<RunResult>}
115
+ */
116
+ const _spawn = (file, args, shell, display, options) =>
117
+ new Promise((resolve, reject) => {
118
+ const started = Date.now();
119
+ const inherit = options.stdio === "inherit";
120
+ const env = options.extendEnv === false ? options.env : { ...process.env, ...options.env };
121
+ const prepared = shell ? { file, args, verbatim: false } : _prepare(file, args);
122
+ const timeoutMs = options.timeout === undefined ? undefined : typeof options.timeout === "number" ? options.timeout : require("./Time.js").parseDuration(options.timeout) ?? undefined;
123
+ if (options.signal?.aborted) return reject(new AbortError(`Command aborted: ${display}`, { cause: options.signal.reason }));
124
+
125
+ const child = spawn(prepared.file, prepared.args, {
126
+ cwd: options.cwd,
127
+ env: /** @type {NodeJS.ProcessEnv} */ (env),
128
+ shell,
129
+ windowsHide: true,
130
+ windowsVerbatimArguments: prepared.verbatim,
131
+ stdio: [options.input !== undefined ? "pipe" : inherit ? "inherit" : "ignore", inherit ? "inherit" : "pipe", inherit ? "inherit" : "pipe"],
132
+ });
133
+
134
+ let stdout = "";
135
+ let stderr = "";
136
+ let timedOut = false;
137
+ /** @type {NodeJS.Timeout | undefined} */
138
+ let killTimer;
139
+ /** @type {NodeJS.Timeout | undefined} */
140
+ let forceTimer;
141
+
142
+ /**
143
+ * @param {import("node:stream").Readable | null} stream
144
+ * @param {(chunk: string) => void} append
145
+ * @param {((line: string) => void) | undefined} onLine
146
+ */
147
+ const collect = (stream, append, onLine) => {
148
+ if (!stream) return;
149
+ stream.setEncoding("utf8");
150
+ let pending = "";
151
+ stream.on("data", (chunk) => {
152
+ append(chunk);
153
+ if (!onLine) return;
154
+ pending += chunk;
155
+ const lines = pending.split(/\r?\n/);
156
+ pending = lines.pop() ?? "";
157
+ for (const line of lines) onLine(line);
158
+ });
159
+ stream.on("end", () => {
160
+ if (onLine && pending) onLine(pending);
161
+ });
162
+ };
163
+ collect(child.stdout, (c) => (stdout += c), options.onStdout);
164
+ collect(child.stderr, (c) => (stderr += c), options.onStderr);
165
+
166
+ if (options.input !== undefined && child.stdin) {
167
+ child.stdin.on("error", () => {});
168
+ child.stdin.end(options.input);
169
+ }
170
+
171
+ const kill = () => {
172
+ child.kill("SIGTERM");
173
+ forceTimer = setTimeout(() => child.kill("SIGKILL"), 2000);
174
+ forceTimer.unref();
175
+ };
176
+ if (timeoutMs !== undefined) {
177
+ killTimer = setTimeout(() => {
178
+ timedOut = true;
179
+ kill();
180
+ }, timeoutMs);
181
+ }
182
+ const onAbort = () => kill();
183
+ options.signal?.addEventListener("abort", onAbort, { once: true });
184
+
185
+ child.on("error", (error) => {
186
+ clearTimeout(killTimer);
187
+ options.signal?.removeEventListener("abort", onAbort);
188
+ reject(new ProcessError(`Failed to start "${display}": ${error.message}`, { command: display, cause: error }));
189
+ });
190
+ child.on("close", (exitCode, signal) => {
191
+ clearTimeout(killTimer);
192
+ clearTimeout(forceTimer);
193
+ options.signal?.removeEventListener("abort", onAbort);
194
+ const trim = options.trim ?? true;
195
+ const result = {
196
+ command: display,
197
+ stdout: trim ? stdout.replace(/\r?\n$/, "") : stdout,
198
+ stderr: trim ? stderr.replace(/\r?\n$/, "") : stderr,
199
+ exitCode,
200
+ signal,
201
+ duration: Date.now() - started,
202
+ };
203
+ if (options.signal?.aborted) return reject(new AbortError(`Command aborted: ${display}`, { cause: options.signal.reason }));
204
+ if (timedOut) return reject(new ProcessError(`Command timed out after ${timeoutMs}ms: ${display}`, { ...result, timedOut: true }));
205
+ if ((options.reject ?? true) && exitCode !== 0) {
206
+ const detail = result.stderr.trim().split(/\r?\n/).slice(-5).join("\n");
207
+ return reject(new ProcessError(`Command failed with ${exitCode === null ? `signal ${signal}` : `exit code ${exitCode}`}: ${display}${detail ? `\n${detail}` : ""}`, result));
208
+ }
209
+ resolve(result);
210
+ });
211
+ });
212
+
213
+ /**
214
+ * Runs a program without a shell, so arguments are passed as they are and
215
+ * user input can't inject anything. Resolves with the output; rejects with
216
+ * a `ProcessError` if the exit code isn't 0.
217
+ *
218
+ * @example
219
+ * const { stdout } = await sys.run("git", ["log", "-1", "--format=%s"]);
220
+ * await sys.run("npm", ["install"], { cwd: "./app", stdio: "inherit" });
221
+ * await sys.run("ffmpeg", ["-i", input, output], { timeout: "5m" });
222
+ *
223
+ * @param {string} file A name found in `PATH`, or a path.
224
+ * @param {string[]} [args=[]]
225
+ * @param {RunOptions} [options]
226
+ * @returns {Promise<RunResult>}
227
+ * @throws {ProcessError} If it can't start, fails (unless `reject: false`) or times out.
228
+ */
229
+ function run(file, args = [], options = {}) {
230
+ const display = [file, ...args.map((a) => (/\s/.test(a) ? JSON.stringify(a) : a))].join(" ");
231
+ return _spawn(file, args.map(String), false, display, options);
232
+ }
233
+
234
+ /**
235
+ * Runs a command line through the shell, so pipes, `&&` and variables
236
+ * work. Don't build it from untrusted input; use `run()` for that.
237
+ *
238
+ * @example
239
+ * const { stdout } = await sys.exec("git status --short | wc -l");
240
+ * const { exitCode } = await sys.exec("npm test", { reject: false, stdio: "inherit" });
241
+ *
242
+ * @param {string} command
243
+ * @param {RunOptions} [options]
244
+ * @returns {Promise<RunResult>}
245
+ * @throws {ProcessError} If it fails (unless `reject: false`) or times out.
246
+ */
247
+ function exec(command, options = {}) {
248
+ return _spawn(command, [], true, command, options);
249
+ }
250
+
251
+ /**
252
+ * Finds an executable in `PATH`, like the `which` command.
253
+ *
254
+ * @example
255
+ * sys.which("git"); // "/usr/bin/git"
256
+ * sys.which("nope"); // undefined
257
+ *
258
+ * @param {string} command
259
+ * @returns {string | undefined}
260
+ */
261
+ function which(command) {
262
+ const isWin = process.platform === "win32";
263
+ const exts = isWin ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
264
+ const candidates = (/** @type {string} */ base) => (isWin && !nodePath.extname(base) ? exts.map((e) => base + e.toLowerCase()) : isWin ? [base, ...exts.map((e) => base + e.toLowerCase())] : [base]);
265
+ const usable = (/** @type {string} */ file) => {
266
+ try {
267
+ if (!fs.statSync(file).isFile()) return false;
268
+ if (!isWin) fs.accessSync(file, fs.constants.X_OK);
269
+ return true;
270
+ } catch {
271
+ return false;
272
+ }
273
+ };
274
+ if (command.includes("/") || command.includes("\\")) {
275
+ return candidates(nodePath.resolve(command)).find(usable);
276
+ }
277
+ for (const dir of (process.env.PATH ?? process.env.Path ?? "").split(nodePath.delimiter)) {
278
+ if (!dir) continue;
279
+ const found = candidates(nodePath.join(dir.replace(/^"|"$/g, ""), command)).find(usable);
280
+ if (found) return found;
281
+ }
282
+ return undefined;
283
+ }
284
+
285
+ /**
286
+ * Opens a URL, file or folder with the default app.
287
+ *
288
+ * @example
289
+ * sys.open("http://localhost:3000");
290
+ *
291
+ * @param {string} target
292
+ * @returns {boolean} `true` if the opener started.
293
+ */
294
+ function open(target) {
295
+ try {
296
+ const value = /^[a-z][a-z\d+.-]*:/i.test(target) ? target : nodePath.resolve(target);
297
+ const child =
298
+ process.platform === "win32"
299
+ ? spawn(process.env.comspec || "cmd.exe", ["/d", "/s", "/c", `start "" ${_escapeArg(value, false)}`], { detached: true, stdio: "ignore", windowsHide: true, windowsVerbatimArguments: true })
300
+ : spawn(process.platform === "darwin" ? "open" : "xdg-open", [value], { detached: true, stdio: "ignore" });
301
+ child.on("error", () => {});
302
+ child.unref();
303
+ return true;
304
+ } catch {
305
+ return false;
306
+ }
307
+ }
308
+
309
+ /** @type {Array<() => unknown>} */
310
+ const _shutdownHandlers = [];
311
+ let _shutdownInstalled = false;
312
+ let _shuttingDown = false;
313
+ /** @type {{ timeout: number, exitCode: number }} */
314
+ const _shutdownConfig = { timeout: 10_000, exitCode: 0 };
315
+
316
+ /**
317
+ * Runs cleanup code when the process is asked to stop (Ctrl+C,
318
+ * `docker stop`, `kill`): close servers, flush logs, disconnect databases.
319
+ * Handlers run in reverse order, then the process exits. If they take
320
+ * longer than `timeout`, the exit is forced; a second Ctrl+C exits at once.
321
+ *
322
+ * @example
323
+ * const server = app.listen(3000);
324
+ * sys.onShutdown(() => new Promise((done) => server.close(done)));
325
+ * sys.onShutdown(() => db.close());
326
+ *
327
+ * @param {() => unknown} handler Can be async.
328
+ * @param {ShutdownOptions} [options]
329
+ * @returns {() => void} Removes the handler.
330
+ */
331
+ function onShutdown(handler, options = {}) {
332
+ if (options.timeout !== undefined) {
333
+ _shutdownConfig.timeout = typeof options.timeout === "number" ? options.timeout : require("./Time.js").parseDuration(options.timeout) ?? 10_000;
334
+ }
335
+ if (options.exitCode !== undefined) _shutdownConfig.exitCode = options.exitCode;
336
+ _shutdownHandlers.push(handler);
337
+ if (!_shutdownInstalled) {
338
+ _shutdownInstalled = true;
339
+ const signals = options.signals ?? /** @type {NodeJS.Signals[]} */ (["SIGINT", "SIGTERM", "SIGHUP", ...(process.platform === "win32" ? ["SIGBREAK"] : [])]);
340
+ for (const signal of signals) {
341
+ try {
342
+ process.on(signal, () => {
343
+ void _shutdown(signal);
344
+ });
345
+ } catch {
346
+ // unsupported signal on this platform
347
+ }
348
+ }
349
+ }
350
+ return () => {
351
+ const index = _shutdownHandlers.lastIndexOf(handler);
352
+ if (index !== -1) _shutdownHandlers.splice(index, 1);
353
+ };
354
+ }
355
+
356
+ /**
357
+ * @param {string} reason
358
+ */
359
+ const _shutdown = async (reason) => {
360
+ if (_shuttingDown) {
361
+ process.exit(1);
362
+ return;
363
+ }
364
+ _shuttingDown = true;
365
+ const force = setTimeout(() => {
366
+ process.stderr.write(`[@ix-xs/node-comfort] Shutdown (${reason}) took longer than ${_shutdownConfig.timeout}ms, forcing exit\n`);
367
+ process.exit(1);
368
+ }, _shutdownConfig.timeout);
369
+ force.unref();
370
+ let failed = false;
371
+ for (const handler of [..._shutdownHandlers].reverse()) {
372
+ try {
373
+ await handler();
374
+ } catch (error) {
375
+ failed = true;
376
+ process.stderr.write(`[@ix-xs/node-comfort] Shutdown handler failed: ${error instanceof Error ? error.stack : String(error)}\n`);
377
+ }
378
+ }
379
+ clearTimeout(force);
380
+ process.exit(failed ? 1 : _shutdownConfig.exitCode);
381
+ };
382
+
383
+ /**
384
+ * Are we on Windows?
385
+ * @returns {boolean}
386
+ */
387
+ function isWindows() {
388
+ return process.platform === "win32";
389
+ }
390
+
391
+ /**
392
+ * Are we on macOS?
393
+ * @returns {boolean}
394
+ */
395
+ function isMac() {
396
+ return process.platform === "darwin";
397
+ }
398
+
399
+ /**
400
+ * Are we on Linux? WSL counts.
401
+ * @returns {boolean}
402
+ */
403
+ function isLinux() {
404
+ return process.platform === "linux";
405
+ }
406
+
407
+ /**
408
+ * Are we running in CI? Detects GitHub Actions, GitLab, CircleCI, Jenkins,
409
+ * Azure, Vercel, Netlify and more.
410
+ *
411
+ * @example
412
+ * if (sys.isCI()) nc.setLevel("warn");
413
+ *
414
+ * @returns {boolean}
415
+ */
416
+ function isCI() {
417
+ const env = process.env;
418
+ if (env.CI === "false" || env.CI === "0") return false;
419
+ return Boolean(
420
+ env.CI || env.CONTINUOUS_INTEGRATION || env.BUILD_NUMBER || env.RUN_ID || env.GITHUB_ACTIONS || env.GITLAB_CI ||
421
+ env.CIRCLECI || env.TRAVIS || env.JENKINS_URL || env.TF_BUILD || env.BITBUCKET_BUILD_NUMBER || env.BUILDKITE ||
422
+ env.VERCEL || env.NETLIFY || env.CODEBUILD_BUILD_ID || env.TEAMCITY_VERSION,
423
+ );
424
+ }
425
+
426
+ /**
427
+ * Are we running inside a container?
428
+ *
429
+ * @returns {boolean}
430
+ */
431
+ function isDocker() {
432
+ if (process.platform !== "linux") return false;
433
+ try {
434
+ if (fs.existsSync("/.dockerenv") || fs.existsSync("/run/.containerenv")) return true;
435
+ return /docker|kubepods|containerd/.test(fs.readFileSync("/proc/self/cgroup", "utf8"));
436
+ } catch {
437
+ return false;
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Are we running in WSL?
443
+ *
444
+ * @returns {boolean}
445
+ */
446
+ function isWSL() {
447
+ return process.platform === "linux" && (/microsoft/i.test(os.release()) || Boolean(process.env.WSL_DISTRO_NAME));
448
+ }
449
+
450
+ /**
451
+ * Facts about the machine and the runtime, handy for bug reports.
452
+ *
453
+ * @example
454
+ * console.table(sys.info());
455
+ *
456
+ * @returns {SystemInfo}
457
+ */
458
+ function info() {
459
+ const cpus = os.cpus();
460
+ let user = "";
461
+ try {
462
+ user = os.userInfo().username;
463
+ } catch {
464
+ user = process.env.USER ?? process.env.USERNAME ?? "";
465
+ }
466
+ return {
467
+ platform: process.platform,
468
+ arch: process.arch,
469
+ release: os.release(),
470
+ node: process.version,
471
+ cpus: cpus.length,
472
+ cpuModel: cpus[0]?.model?.trim() ?? "unknown",
473
+ memory: { total: os.totalmem(), free: os.freemem(), used: os.totalmem() - os.freemem() },
474
+ uptime: os.uptime(),
475
+ hostname: os.hostname(),
476
+ user,
477
+ pid: process.pid,
478
+ ci: isCI(),
479
+ docker: isDocker(),
480
+ wsl: isWSL(),
481
+ };
482
+ }
483
+
484
+ /**
485
+ * Memory used by this process, in bytes.
486
+ *
487
+ * @example
488
+ * nc.num.formatBytes(sys.memory().heapUsed); // "42.1 MB"
489
+ *
490
+ * @returns {{ rss: number, heapTotal: number, heapUsed: number, external: number, arrayBuffers: number }}
491
+ */
492
+ function memory() {
493
+ const usage = process.memoryUsage();
494
+ return { rss: usage.rss, heapTotal: usage.heapTotal, heapUsed: usage.heapUsed, external: usage.external, arrayBuffers: usage.arrayBuffers };
495
+ }
496
+
497
+ module.exports = {
498
+ run,
499
+ exec,
500
+ which,
501
+ open,
502
+ onShutdown,
503
+ isWindows,
504
+ isMac,
505
+ isLinux,
506
+ isCI,
507
+ isDocker,
508
+ isWSL,
509
+ info,
510
+ memory,
511
+ };