@goodbyenjn/utils 26.1.1 → 26.1.3

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://badge.fury.io/js/@goodbyenjn%2Futils.svg)](https://badge.fury.io/js/@goodbyenjn%2Futils)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- A modern TypeScript/JavaScript utility library providing a comprehensive collection of type-safe utility functions, functional error handling with the Result pattern, and filesystem operations.
6
+ A modern TypeScript/JavaScript utility library providing a comprehensive collection of type-safe utility functions, functional error handling with the Result pattern, filesystem operations, and shell command execution.
7
7
 
8
8
  ## Features
9
9
 
@@ -12,7 +12,8 @@ A modern TypeScript/JavaScript utility library providing a comprehensive collect
12
12
  - 📦 **Modular**: Import only what you need with tree-shakable exports and multiple entry points
13
13
  - 🛡️ **Result Pattern**: Functional error handling without exceptions, based on Rust-style Result types
14
14
  - 📁 **Safe File System**: Type-safe file system operations with Result-based error handling
15
- - 🧰 **Common Utilities**: String manipulation, math operations, promise utilities, shell commands, and error handling
15
+ - 🐚 **Shell Execution**: Powerful and flexible shell command execution with piping support
16
+ - 🧰 **Common Utilities**: String manipulation, math operations, promise utilities, and error handling
16
17
  - 📊 **Remeda Extensions**: Extended utilities built on top of [Remeda](https://remedajs.com/)
17
18
 
18
19
  ## Installation
@@ -31,7 +32,8 @@ yarn add @goodbyenjn/utils
31
32
 
32
33
  ```typescript
33
34
  // Import what you need from the main module
34
- import { sleep, template, $ } from "@goodbyenjn/utils";
35
+ import { sleep, template } from "@goodbyenjn/utils";
36
+ import { $ } from "@goodbyenjn/utils/shell";
35
37
  import { safeReadFile } from "@goodbyenjn/utils/fs";
36
38
  import { ok, Result } from "@goodbyenjn/utils/result";
37
39
  ```
@@ -110,6 +112,10 @@ const cleaned = removeSuffix(".js", "script.js"); // "script"
110
112
  // String joining and splitting
111
113
  const path = join("/", "home", "user", "docs"); // "/home/user/docs"
112
114
  const parts = split("-", "hello-world-js"); // ["hello", "world", "js"]
115
+
116
+ // Split string by line breaks (handles both \n and \r\n)
117
+ const lines = splitByLineBreak("line1\nline2\r\nline3");
118
+ console.log(lines); // ["line1", "line2", "line3"]
113
119
  ```
114
120
 
115
121
  #### Promise Utilities
@@ -147,30 +153,32 @@ const result = await promise;
147
153
  #### Shell Command Execution
148
154
 
149
155
  ```typescript
150
- import { $ } from "@goodbyenjn/utils";
151
-
152
- // Execute shell commands safely using template literals
153
- const result = await $`ls -la`;
154
- if (result.isOk()) {
155
- console.log("Command succeeded:");
156
- console.log("stdout:", result.unwrap().stdout);
157
- console.log("stderr:", result.unwrap().stderr);
158
- } else if (result.isErr()) {
159
- console.error("Command failed:", result.unwrapErr().message);
160
- }
156
+ import { $ } from "@goodbyenjn/utils/shell";
157
+
158
+ // Execute shell commands with template literals
159
+ const result = await $`npm install`;
160
+ console.log(result.stdout);
161
+ console.log(result.stderr);
161
162
 
162
- // Complex command with options
163
- const { $ } = await import("@goodbyenjn/utils");
164
- const deployResult = await $`docker build -t myapp .`;
165
- if (deployResult.isOk()) {
166
- const { stdout, stderr } = deployResult.unwrap();
167
- console.log("Build output:", stdout);
163
+ // String command with args
164
+ const output = await $("ls", ["-la"]);
165
+ console.log(output.stdout);
166
+
167
+ // Pipe commands
168
+ const piped = await $`echo "hello"`.pipe`cat`;
169
+ console.log(piped.stdout);
170
+
171
+ // Iterate output line by line
172
+ for await (const line of $`cat large-file.txt`) {
173
+ console.log(line);
168
174
  }
169
175
 
170
- // Using quoteShellArg for safe argument escaping
171
- import { quoteShellArg } from "@goodbyenjn/utils";
172
- const userInput = "'; rm -rf /;";
173
- const safeArg = quoteShellArg(userInput); // Properly escaped for shell
176
+ // Using options
177
+ const result2 = await $("npm", ["install"], { cwd: "/path/to/project" });
178
+
179
+ // Factory function with options
180
+ const withCwd = $({ cwd: "/path/to/project" });
181
+ const result3 = await withCwd`npm install`;
174
182
  ```
175
183
 
176
184
  #### Math Utilities
@@ -499,6 +507,7 @@ Common utilities for everyday programming tasks:
499
507
  export {
500
508
  template,
501
509
  unindent,
510
+ indent,
502
511
  addPrefix,
503
512
  addSuffix,
504
513
  removePrefix,
@@ -508,6 +517,7 @@ export {
508
517
  splitWithSlash,
509
518
  joinWithSlash,
510
519
  toForwardSlash,
520
+ splitByLineBreak,
511
521
  concatTemplateStrings,
512
522
  };
513
523
 
@@ -984,8 +984,8 @@ const unindent = (...params) => {
984
984
  const line = lines[i];
985
985
  if (REGEXP_WHITESPACE_ONLY.test(line)) continue;
986
986
  if (firstContentLine === -1) firstContentLine = i;
987
- const indent = line.match(REGEXP_WHITESPACE_PREFIX)?.[0].length ?? 0;
988
- commonIndent = Math.min(commonIndent, indent);
987
+ const indent$1 = line.match(REGEXP_WHITESPACE_PREFIX)?.[0].length ?? 0;
988
+ commonIndent = Math.min(commonIndent, indent$1);
989
989
  }
990
990
  if (firstContentLine === -1) commonIndent = 0;
991
991
  if (!Number.isFinite(commonIndent)) commonIndent = 0;
@@ -1001,7 +1001,57 @@ const unindent = (...params) => {
1001
1001
  return unindentImpl;
1002
1002
  }
1003
1003
  if (e$1(params[0]) || e$6(params[0])) return unindentImpl(...params);
1004
- throw new TypeError("Invalid arguments.");
1004
+ throw new TypeError(`First parameter has an invalid type: "${typeof params[0]}"`);
1005
+ };
1006
+ /**
1007
+ * @example
1008
+ * ```ts
1009
+ * // Using indent count with default space character
1010
+ * const str1 = indent(2)`
1011
+ * if (a) {
1012
+ * b()
1013
+ * }
1014
+ * `;
1015
+ *
1016
+ * // Using custom indent string directly
1017
+ * const str2 = indent(">>")`
1018
+ * if (a) {
1019
+ * b()
1020
+ * }
1021
+ * `;
1022
+ *
1023
+ * // Only trim start, keep end
1024
+ * const str3 = indent(2, true, false)("hello\nworld\n");
1025
+ * ```
1026
+ */
1027
+ const indent = (...params) => {
1028
+ let indentString;
1029
+ let trimStart = true;
1030
+ let trimEnd = true;
1031
+ const indentImpl = (...params$1) => {
1032
+ const lines = splitByLineBreak(e$1(params$1[0]) ? params$1[0] : concatTemplateStrings(params$1[0], params$1.slice(1)));
1033
+ const whitespaceLines = lines.map((line) => REGEXP_WHITESPACE_ONLY.test(line));
1034
+ let startIndex = 0;
1035
+ let endIndex = lines.length;
1036
+ if (trimStart) while (startIndex < lines.length && whitespaceLines[startIndex]) startIndex++;
1037
+ if (trimEnd) while (endIndex > startIndex && whitespaceLines[endIndex - 1]) endIndex--;
1038
+ return lines.slice(startIndex, endIndex).map((line, index) => {
1039
+ return whitespaceLines[index + startIndex] ? line : indentString + line;
1040
+ }).join("\n");
1041
+ };
1042
+ if ((e$4(params[1]) || params[1] === void 0) && (e$4(params[2]) || params[2] === void 0)) {
1043
+ trimStart = params[1] !== false;
1044
+ trimEnd = params[2] !== false;
1045
+ }
1046
+ if (e$2(params[0])) {
1047
+ indentString = " ".repeat(params[0]);
1048
+ return indentImpl;
1049
+ }
1050
+ if (e$1(params[0])) {
1051
+ indentString = params[0];
1052
+ return indentImpl;
1053
+ }
1054
+ throw new TypeError(`First parameter has an invalid type: "${typeof params[0]}"`);
1005
1055
  };
1006
1056
  function template(str, ...args) {
1007
1057
  const [firstArg, fallback] = args;
@@ -1015,74 +1065,6 @@ function template(str, ...args) {
1015
1065
  });
1016
1066
  }
1017
1067
 
1018
- //#endregion
1019
- //#region src/common/shell.ts
1020
- const REGEXP_NULL_CHAR = /\x00+/g;
1021
- const REGEXP_SAFE_CHARS = /^[A-Za-z0-9,:=_./-]+$/;
1022
- const REGEXP_SINGLE_QUOTES = /'+/g;
1023
- const noop = () => {};
1024
- const pipeToStdout = (chunk) => process.stdout.write(chunk);
1025
- const pipeToStderr = (chunk) => process.stderr.write(chunk);
1026
- async function $(cmd, ...values) {
1027
- const { spawn } = await import("node:child_process");
1028
- const [command, options] = e$1(cmd) ? [cmd, values[0] || {}] : [concatTemplateStrings(cmd, values), {}];
1029
- const stdio = [
1030
- "inherit",
1031
- "pipe",
1032
- "pipe"
1033
- ];
1034
- let onStdin = noop;
1035
- if (options.onStdin !== void 0) {
1036
- stdio[0] = "pipe";
1037
- if (isFunction(options.onStdin)) onStdin = options.onStdin;
1038
- else {
1039
- const chunk = options.onStdin;
1040
- onStdin = (stdin) => stdin.write(chunk);
1041
- }
1042
- }
1043
- const onStdout = options.onStdout === "ignore" ? noop : options.onStdout === "print" ? pipeToStdout : options.onStdout || noop;
1044
- const onStderr = options.onStderr === "ignore" ? noop : options.onStderr === "print" ? pipeToStderr : options.onStderr || noop;
1045
- const fn = async () => {
1046
- const { promise, reject, resolve } = createPromiseWithResolvers();
1047
- const child = spawn(command, {
1048
- shell: true,
1049
- stdio
1050
- });
1051
- if (stdio[0] === "pipe" && child.stdin) {
1052
- await onStdin(child.stdin);
1053
- child.stdin?.end();
1054
- }
1055
- let stdout = "";
1056
- let stderr = "";
1057
- child.stdout?.on("data", (data) => {
1058
- const chunk = data.toString();
1059
- stdout += chunk;
1060
- onStdout(chunk);
1061
- });
1062
- child.stderr?.on("data", (data) => {
1063
- const chunk = data.toString();
1064
- stderr += chunk;
1065
- onStderr(chunk);
1066
- });
1067
- child.on("error", reject);
1068
- child.on("close", (code) => {
1069
- if (code === 0) resolve({
1070
- stdout: stdout.trim(),
1071
- stderr: stderr.trim()
1072
- });
1073
- else reject(/* @__PURE__ */ new Error(`Command exited with code ${code}`));
1074
- });
1075
- return await promise;
1076
- };
1077
- return (await Result.fromCallable(fn, Error)).context(`Failed to execute command: ${cmd}`);
1078
- }
1079
- const quoteShellArg = (arg) => {
1080
- if (!arg) return "''";
1081
- const cleaned = String(arg).replace(REGEXP_NULL_CHAR, "");
1082
- if (REGEXP_SAFE_CHARS.exec(cleaned)?.[0].length === cleaned.length) return cleaned;
1083
- return `'${cleaned.replace(REGEXP_SINGLE_QUOTES, (matched) => matched.length === 1 ? `'\\''` : `'"${matched}"'`)}'`.replace(/^''/, "").replace(/''$/, "");
1084
- };
1085
-
1086
1068
  //#endregion
1087
1069
  //#region src/common/throttle.ts
1088
1070
  const wrap = (fn, wait, options) => {
@@ -1118,4 +1100,4 @@ const throttle = (fn, wait = 0, options = {}) => {
1118
1100
  };
1119
1101
 
1120
1102
  //#endregion
1121
- export { Result as A, linear as C, safeParse as D, normalizeError as E, __commonJSMin as F, __toESM as I, ok as M, safeTry as N, stringify as O, ResultError as P, parseValueToBoolean as S, getErrorMessage as T, createLock as _, addPrefix as a, sleep as b, join as c, removeSuffix as d, split as f, unindent as g, toForwardSlash as h, quoteShellArg as i, err as j, unsafeParse as k, joinWithSlash as l, template as m, throttle as n, addSuffix as o, splitWithSlash as p, $ as r, concatTemplateStrings as s, debounce as t, removePrefix as u, createPromiseWithResolvers as v, scale as w, parseKeyValuePairs as x, createSingleton as y };
1103
+ export { Result as A, linear as C, safeParse as D, normalizeError as E, __commonJSMin as F, __toESM as I, ok as M, safeTry as N, stringify as O, ResultError as P, parseValueToBoolean as S, getErrorMessage as T, createLock as _, concatTemplateStrings as a, sleep as b, joinWithSlash as c, split as d, splitByLineBreak as f, unindent as g, toForwardSlash as h, addSuffix as i, err as j, unsafeParse as k, removePrefix as l, template as m, throttle as n, indent as o, splitWithSlash as p, addPrefix as r, join as s, debounce as t, removeSuffix as u, createPromiseWithResolvers as v, scale as w, parseKeyValuePairs as x, createSingleton as y };
package/dist/common.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { i as Result } from "./chunks/chunk-4b76d1c4.js";
2
- import { ai as Fn, on as Promisable, ri as AsyncFn } from "./chunks/chunk-a07ed28f.js";
2
+ import { ai as Fn, ri as AsyncFn } from "./chunks/chunk-a07ed28f.js";
3
3
 
4
4
  //#region src/common/error.d.ts
5
5
  declare const normalizeError: (error: unknown, caller?: Function) => Error;
@@ -82,20 +82,6 @@ declare const createSingleton: <T>(fn: AsyncFn<T>) => Singleton<T>;
82
82
  declare const createLock: () => Lock;
83
83
  declare const createPromiseWithResolvers: <T>() => PromiseWithResolvers<T>;
84
84
  //#endregion
85
- //#region src/common/shell.d.ts
86
- interface ShellExecOptions {
87
- onStdin?: string | Buffer | ((stdin: NodeJS.WritableStream) => Promisable<any>);
88
- onStdout?: "ignore" | "print" | ((chunk: string) => Promisable<any>);
89
- onStderr?: "ignore" | "print" | ((chunk: string) => Promisable<any>);
90
- }
91
- interface ShellExecResult {
92
- stdout: string;
93
- stderr: string;
94
- }
95
- declare function $(cmd: string, options?: ShellExecOptions): Promise<Result<ShellExecResult, Error>>;
96
- declare function $(cmd: TemplateStringsArray, ...values: any[]): Promise<Result<ShellExecResult, Error>>;
97
- declare const quoteShellArg: (arg: string) => string;
98
- //#endregion
99
85
  //#region src/common/string.d.ts
100
86
  declare const addPrefix: (prefix: string, str: string) => string;
101
87
  declare const addSuffix: (suffix: string, str: string) => string;
@@ -106,6 +92,7 @@ declare const split: (separator: string, path: string) => string[];
106
92
  declare const toForwardSlash: (str: string) => string;
107
93
  declare const joinWithSlash: (...paths: string[]) => string;
108
94
  declare const splitWithSlash: (path: string) => string[];
95
+ declare const splitByLineBreak: (str: string) => string[];
109
96
  declare const concatTemplateStrings: (template: TemplateStringsArray, values: any[]) => string;
110
97
  interface StringOrTemplateFunction {
111
98
  (str: string): string;
@@ -136,6 +123,32 @@ interface UnindentFunction extends StringOrTemplateFunction {
136
123
  * ```
137
124
  */
138
125
  declare const unindent: UnindentFunction;
126
+ interface IndentFunction {
127
+ (indentNumber: number, trimStart?: boolean, trimEnd?: boolean): StringOrTemplateFunction;
128
+ (indentString: string, trimStart?: boolean, trimEnd?: boolean): StringOrTemplateFunction;
129
+ }
130
+ /**
131
+ * @example
132
+ * ```ts
133
+ * // Using indent count with default space character
134
+ * const str1 = indent(2)`
135
+ * if (a) {
136
+ * b()
137
+ * }
138
+ * `;
139
+ *
140
+ * // Using custom indent string directly
141
+ * const str2 = indent(">>")`
142
+ * if (a) {
143
+ * b()
144
+ * }
145
+ * `;
146
+ *
147
+ * // Only trim start, keep end
148
+ * const str3 = indent(2, true, false)("hello\nworld\n");
149
+ * ```
150
+ */
151
+ declare const indent: IndentFunction;
139
152
  /**
140
153
  * @example
141
154
  * ```
@@ -179,4 +192,4 @@ type ThrottleOptions = Options;
179
192
  declare const debounce: <T extends Fn>(fn: T, wait?: number, options?: DebounceOptions) => DebouncedFn<T>;
180
193
  declare const throttle: <T extends Fn>(fn: T, wait?: number, options?: ThrottleOptions) => ThrottledFn<T>;
181
194
  //#endregion
182
- export { $, type DebounceOptions, type DebouncedFn, type Lock, type PromiseWithResolvers, type ShellExecOptions, type ShellExecResult, type Singleton, type Stringify, type ThrottleOptions, type ThrottledFn, addPrefix, addSuffix, concatTemplateStrings, createLock, createPromiseWithResolvers, createSingleton, debounce, getErrorMessage, join, joinWithSlash, linear, normalizeError, unsafeParse as parse, parseKeyValuePairs, parseValueToBoolean, quoteShellArg, removePrefix, removeSuffix, safeParse, scale, sleep, split, splitWithSlash, stringify, template, throttle, toForwardSlash, unindent };
195
+ export { type DebounceOptions, type DebouncedFn, type Lock, type PromiseWithResolvers, type Singleton, type Stringify, type ThrottleOptions, type ThrottledFn, addPrefix, addSuffix, concatTemplateStrings, createLock, createPromiseWithResolvers, createSingleton, debounce, getErrorMessage, indent, join, joinWithSlash, linear, normalizeError, unsafeParse as parse, parseKeyValuePairs, parseValueToBoolean, removePrefix, removeSuffix, safeParse, scale, sleep, split, splitByLineBreak, splitWithSlash, stringify, template, throttle, toForwardSlash, unindent };
package/dist/common.js CHANGED
@@ -1,3 +1,3 @@
1
- import { C as linear, D as safeParse, E as normalizeError, O as stringify, S as parseValueToBoolean, T as getErrorMessage, _ as createLock, a as addPrefix, b as sleep, c as join, d as removeSuffix, f as split, g as unindent, h as toForwardSlash, i as quoteShellArg, k as unsafeParse, l as joinWithSlash, m as template, n as throttle, o as addSuffix, p as splitWithSlash, r as $, s as concatTemplateStrings, t as debounce, u as removePrefix, v as createPromiseWithResolvers, w as scale, x as parseKeyValuePairs, y as createSingleton } from "./chunks/chunk-7a37e668.js";
1
+ import { C as linear, D as safeParse, E as normalizeError, O as stringify, S as parseValueToBoolean, T as getErrorMessage, _ as createLock, a as concatTemplateStrings, b as sleep, c as joinWithSlash, d as split, f as splitByLineBreak, g as unindent, h as toForwardSlash, i as addSuffix, k as unsafeParse, l as removePrefix, m as template, n as throttle, o as indent, p as splitWithSlash, r as addPrefix, s as join, t as debounce, u as removeSuffix, v as createPromiseWithResolvers, w as scale, x as parseKeyValuePairs, y as createSingleton } from "./chunks/chunk-fda806f4.js";
2
2
 
3
- export { $, addPrefix, addSuffix, concatTemplateStrings, createLock, createPromiseWithResolvers, createSingleton, debounce, getErrorMessage, join, joinWithSlash, linear, normalizeError, unsafeParse as parse, parseKeyValuePairs, parseValueToBoolean, quoteShellArg, removePrefix, removeSuffix, safeParse, scale, sleep, split, splitWithSlash, stringify, template, throttle, toForwardSlash, unindent };
3
+ export { addPrefix, addSuffix, concatTemplateStrings, createLock, createPromiseWithResolvers, createSingleton, debounce, getErrorMessage, indent, join, joinWithSlash, linear, normalizeError, unsafeParse as parse, parseKeyValuePairs, parseValueToBoolean, removePrefix, removeSuffix, safeParse, scale, sleep, split, splitByLineBreak, splitWithSlash, stringify, template, throttle, toForwardSlash, unindent };
package/dist/fs.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as Result, D as safeParse, F as __commonJSMin, I as __toESM, M as ok, N as safeTry, O as stringify, j as err, u as removePrefix } from "./chunks/chunk-7a37e668.js";
1
+ import { A as Result, D as safeParse, F as __commonJSMin, I as __toESM, M as ok, N as safeTry, O as stringify, j as err, l as removePrefix } from "./chunks/chunk-fda806f4.js";
2
2
  import { Fn as e$1, Jt as n, Sn as e$2, gn as e } from "./chunks/chunk-267b337b.js";
3
3
  import * as nativeFs$1 from "fs";
4
4
  import nativeFs from "fs";
package/dist/result.js CHANGED
@@ -1,3 +1,3 @@
1
- import { A as Result, M as ok, N as safeTry, P as ResultError, j as err } from "./chunks/chunk-7a37e668.js";
1
+ import { A as Result, M as ok, N as safeTry, P as ResultError, j as err } from "./chunks/chunk-fda806f4.js";
2
2
 
3
3
  export { Result, ResultError, err, ok, safeTry };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodbyenjn/utils",
3
- "version": "26.1.1",
3
+ "version": "26.1.3",
4
4
  "description": "GoodbyeNJN's utils for typescript and javascript",
5
5
  "keywords": [
6
6
  "utils",
@@ -60,6 +60,7 @@
60
60
  "@goodbyenjn/configs": "^6.1.4",
61
61
  "@goodbyenjn/utils": "file:",
62
62
  "@types/node": "^25.0.3",
63
+ "args-tokenizer": "^0.3.0",
63
64
  "eslint": "^9.39.2",
64
65
  "prettier": "^3.7.4",
65
66
  "remeda": "^2.33.1",
@@ -67,6 +68,7 @@
67
68
  "rolldown-plugin-dts": "^0.20.0",
68
69
  "rotery": "^0.7.0",
69
70
  "safe-stable-stringify": "^2.5.0",
71
+ "tinyexec": "^1.0.2",
70
72
  "tinyglobby": "^0.2.15",
71
73
  "type-fest": "^5.3.1",
72
74
  "typescript": "^5.9.3",