@vibe-agent-toolkit/utils 0.1.0-rc.7

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.
@@ -0,0 +1,112 @@
1
+ import path from 'node:path';
2
+ /**
3
+ * Normalize a path for cross-platform comparison
4
+ *
5
+ * - Converts to absolute path (if baseDir provided)
6
+ * - Normalizes separators (/ vs \)
7
+ * - Resolves . and ..
8
+ * - Removes trailing slashes
9
+ *
10
+ * @param p - Path to normalize
11
+ * @param baseDir - Optional base directory for relative path resolution
12
+ * @returns Normalized absolute path
13
+ *
14
+ * @example
15
+ * normalizePath('./docs/../README.md', '/project')
16
+ * // Returns: '/project/README.md'
17
+ */
18
+ export function normalizePath(p, baseDir) {
19
+ // Resolve to absolute if baseDir provided, otherwise just normalize
20
+ const resolved = baseDir ? path.resolve(baseDir, p) : path.normalize(p);
21
+ // Normalize path separators and remove trailing slashes
22
+ // Use simple non-backtracking pattern
23
+ let normalized = resolved;
24
+ while (normalized.endsWith('/') || normalized.endsWith('\\')) {
25
+ normalized = normalized.slice(0, -1);
26
+ }
27
+ return normalized;
28
+ }
29
+ /**
30
+ * Check if a path is absolute
31
+ *
32
+ * Cross-platform detection of absolute paths:
33
+ * - Unix: /path/to/file
34
+ * - Windows: C:\path\to\file or C:/path/to/file
35
+ *
36
+ * @param p - Path to check
37
+ * @returns True if path is absolute
38
+ *
39
+ * @example
40
+ * isAbsolutePath('/path/to/file') // true
41
+ * isAbsolutePath('./relative') // false
42
+ * isAbsolutePath('C:/Windows') // true (Windows)
43
+ */
44
+ export function isAbsolutePath(p) {
45
+ return path.isAbsolute(p);
46
+ }
47
+ /**
48
+ * Convert a relative path to absolute
49
+ *
50
+ * If path is already absolute, returns it normalized.
51
+ * Otherwise resolves relative to baseDir.
52
+ *
53
+ * @param p - Path to convert
54
+ * @param baseDir - Base directory for resolution
55
+ * @returns Absolute path
56
+ *
57
+ * @example
58
+ * toAbsolutePath('./docs/README.md', '/project')
59
+ * // Returns: '/project/docs/README.md'
60
+ *
61
+ * toAbsolutePath('/absolute/path.md', '/project')
62
+ * // Returns: '/absolute/path.md'
63
+ */
64
+ export function toAbsolutePath(p, baseDir) {
65
+ if (path.isAbsolute(p)) {
66
+ return path.normalize(p);
67
+ }
68
+ return path.resolve(baseDir, p);
69
+ }
70
+ /**
71
+ * Get the relative path from one file to another
72
+ *
73
+ * Useful for generating relative links between markdown files.
74
+ *
75
+ * @param from - Source file path (absolute)
76
+ * @param to - Target file path (absolute)
77
+ * @returns Relative path from source to target
78
+ *
79
+ * @example
80
+ * getRelativePath('/project/docs/guide.md', '/project/README.md')
81
+ * // Returns: '../README.md'
82
+ *
83
+ * getRelativePath('/project/README.md', '/project/docs/api.md')
84
+ * // Returns: 'docs/api.md'
85
+ */
86
+ export function getRelativePath(from, to) {
87
+ // Get directory of source file (not the file itself)
88
+ const fromDir = path.dirname(from);
89
+ // Calculate relative path from source directory to target file
90
+ return path.relative(fromDir, to);
91
+ }
92
+ /**
93
+ * Convert a path to Unix-style forward slashes
94
+ *
95
+ * Useful for glob pattern matching, which expects forward slashes.
96
+ * On Windows, path.resolve() and path.normalize() return backslashes,
97
+ * but glob matchers like picomatch expect forward slashes by default.
98
+ *
99
+ * @param p - Path to convert
100
+ * @returns Path with forward slashes
101
+ *
102
+ * @example
103
+ * toUnixPath('C:\\Users\\docs\\README.md')
104
+ * // Returns: 'C:/Users/docs/README.md'
105
+ *
106
+ * toUnixPath('/project/docs/README.md')
107
+ * // Returns: '/project/docs/README.md' (unchanged on Unix)
108
+ */
109
+ export function toUnixPath(p) {
110
+ return p.replaceAll('\\', '/');
111
+ }
112
+ //# sourceMappingURL=path-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS,EAAE,OAAgB;IACvD,oEAAoE;IACpE,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAExE,wDAAwD;IACxD,sCAAsC;IACtC,IAAI,UAAU,GAAG,QAAQ,CAAC;IAC1B,OAAO,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7D,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,cAAc,CAAC,CAAS,EAAE,OAAe;IACvD,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,EAAU;IACtD,qDAAqD;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC,+DAA+D;IAC/D,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Options for safe command execution
3
+ */
4
+ export interface SafeExecOptions {
5
+ /** Character encoding for output (default: undefined = Buffer) */
6
+ encoding?: BufferEncoding;
7
+ /** Standard I/O configuration */
8
+ stdio?: 'pipe' | 'ignore' | Array<'pipe' | 'ignore' | 'inherit'>;
9
+ /** Environment variables (merged with process.env if not fully specified) */
10
+ env?: NodeJS.ProcessEnv;
11
+ /** Working directory */
12
+ cwd?: string;
13
+ /** Maximum output buffer size in bytes */
14
+ maxBuffer?: number;
15
+ /** Timeout in milliseconds */
16
+ timeout?: number;
17
+ }
18
+ /**
19
+ * Result of a safe command execution
20
+ */
21
+ export interface SafeExecResult {
22
+ /** Exit code (0 = success) */
23
+ status: number;
24
+ /** Standard output */
25
+ stdout: Buffer | string;
26
+ /** Standard error */
27
+ stderr: Buffer | string;
28
+ /** Error object if command failed to spawn */
29
+ error?: Error;
30
+ }
31
+ /**
32
+ * Error thrown when command execution fails
33
+ */
34
+ export declare class CommandExecutionError extends Error {
35
+ readonly status: number;
36
+ readonly stdout: Buffer | string;
37
+ readonly stderr: Buffer | string;
38
+ constructor(message: string, status: number, stdout: Buffer | string, stderr: Buffer | string);
39
+ }
40
+ /**
41
+ * Safe command execution using spawnSync + which pattern
42
+ *
43
+ * More secure than execSync:
44
+ * - Resolves PATH once using pure Node.js (which package)
45
+ * - Executes with absolute path and shell: false
46
+ * - No shell interpreter = no command injection risk
47
+ * - Supports custom env vars (e.g., GIT_INDEX_FILE)
48
+ *
49
+ * @param command - Command name (e.g., 'git', 'gitleaks', 'node')
50
+ * @param args - Array of arguments
51
+ * @param options - Execution options
52
+ * @returns Buffer or string output
53
+ * @throws Error if command not found or execution fails
54
+ *
55
+ * @example
56
+ * // Tool detection
57
+ * safeExecSync('gitleaks', ['--version'], { stdio: 'ignore' });
58
+ *
59
+ * @example
60
+ * // Git with custom env
61
+ * safeExecSync('git', ['add', '--all'], {
62
+ * env: { ...process.env, GIT_INDEX_FILE: tempFile }
63
+ * });
64
+ *
65
+ * @example
66
+ * // Get output as string
67
+ * const version = safeExecSync('node', ['--version'], { encoding: 'utf8' });
68
+ */
69
+ export declare function safeExecSync(command: string, args?: string[], options?: SafeExecOptions): Buffer | string;
70
+ /**
71
+ * Safe command execution that returns detailed result (doesn't throw)
72
+ *
73
+ * Use this when you need to handle errors programmatically
74
+ * instead of catching exceptions.
75
+ *
76
+ * @param command - Command name (e.g., 'git', 'node')
77
+ * @param args - Array of arguments
78
+ * @param options - Execution options
79
+ * @returns Detailed execution result
80
+ *
81
+ * @example
82
+ * const result = safeExecResult('git', ['status']);
83
+ * if (result.status === 0) {
84
+ * console.log(result.stdout.toString());
85
+ * } else {
86
+ * console.error(`Failed: ${result.stderr.toString()}`);
87
+ * }
88
+ */
89
+ export declare function safeExecResult(command: string, args?: string[], options?: SafeExecOptions): SafeExecResult;
90
+ /**
91
+ * Check if a command-line tool is available
92
+ *
93
+ * @param toolName - Name of tool to check (e.g., 'gh', 'gitleaks', 'node')
94
+ * @returns true if tool is available, false otherwise
95
+ *
96
+ * @example
97
+ * if (isToolAvailable('gh')) {
98
+ * console.log('GitHub CLI is installed');
99
+ * }
100
+ */
101
+ export declare function isToolAvailable(toolName: string): boolean;
102
+ /**
103
+ * Get tool version if available
104
+ *
105
+ * @param toolName - Name of tool (e.g., 'node', 'pnpm')
106
+ * @param versionArg - Argument to get version (default: '--version')
107
+ * @returns Version string or null if not available
108
+ *
109
+ * @example
110
+ * const nodeVersion = getToolVersion('node');
111
+ * console.log(nodeVersion); // "v20.11.0"
112
+ *
113
+ * @example
114
+ * const gitVersion = getToolVersion('git', 'version');
115
+ * console.log(gitVersion); // "git version 2.39.2"
116
+ */
117
+ export declare function getToolVersion(toolName: string, versionArg?: string): string | null;
118
+ /**
119
+ * Check if a command string contains shell-specific syntax
120
+ *
121
+ * Detects patterns that require shell interpretation:
122
+ * - Quotes (", ', `)
123
+ * - Glob patterns (*, ?, [])
124
+ * - Variable expansion ($)
125
+ * - Pipes/redirects/operators (|, >, <, &, ;, &&, ||)
126
+ *
127
+ * Performance: Single-pass O(n) algorithm with O(1) Set lookups.
128
+ * Short-circuits on first match (no backtracking, no regex overhead).
129
+ *
130
+ * @param commandString - Command string to check
131
+ * @returns Object with detection result and details
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * const check1 = hasShellSyntax('npm test');
136
+ * console.log(check1); // { hasShellSyntax: false }
137
+ *
138
+ * const check2 = hasShellSyntax('npm test && npm run build');
139
+ * console.log(check2);
140
+ * // {
141
+ * // hasShellSyntax: true,
142
+ * // pattern: 'pipes/redirects/operators',
143
+ * // example: 'cat file | grep text'
144
+ * // }
145
+ * ```
146
+ */
147
+ export declare function hasShellSyntax(commandString: string): {
148
+ hasShellSyntax: boolean;
149
+ pattern?: string;
150
+ example?: string;
151
+ };
152
+ /**
153
+ * Execute a command from a simple command string (convenience wrapper)
154
+ *
155
+ * **IMPORTANT: Shift-Left Validation** - This function actively rejects shell syntax
156
+ * to prevent subtle bugs where shell features are expected but not executed.
157
+ *
158
+ * **Supported:**
159
+ * - Simple commands: `git status`, `pnpm test`, `node --version`
160
+ * - Commands with flags: `git log --oneline --max-count 10`
161
+ * - Multiple unquoted arguments: `gh pr view 123`
162
+ *
163
+ * **NOT Supported (will throw error):**
164
+ * - Quotes: `echo "hello world"` ❌
165
+ * - Glob patterns: `ls *.txt` ❌
166
+ * - Variable expansion: `echo $HOME` ❌
167
+ * - Pipes/redirects: `cat file | grep text` ❌
168
+ * - Command chaining: `build && test` ❌
169
+ *
170
+ * **Why these restrictions?**
171
+ * We don't use a shell interpreter (for security), so shell features like
172
+ * glob expansion, variable substitution, and pipes don't work. By detecting
173
+ * and rejecting these patterns, we force you to use the safer `safeExecSync()`
174
+ * API with explicit argument arrays.
175
+ *
176
+ * @param commandString - Simple command string (no shell syntax)
177
+ * @param options - Execution options
178
+ * @returns Command output (Buffer or string depending on encoding option)
179
+ * @throws Error if command contains shell-specific syntax
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * // ✅ Simple commands (these work)
184
+ * safeExecFromString('git status');
185
+ * safeExecFromString('pnpm test --watch');
186
+ * safeExecFromString('gh pr view 123');
187
+ * ```
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * // ❌ Shell syntax (these throw errors)
192
+ * safeExecFromString('echo "hello"'); // Quotes
193
+ * safeExecFromString('ls *.txt'); // Glob pattern
194
+ * safeExecFromString('cat file | grep text'); // Pipe
195
+ * safeExecFromString('echo $HOME'); // Variable expansion
196
+ *
197
+ * // ✅ Use safeExecSync() instead with explicit arguments
198
+ * safeExecSync('echo', ['hello']);
199
+ * safeExecSync('ls', ['file1.txt', 'file2.txt']); // Or use glob library
200
+ * safeExecSync('grep', ['text', 'file']);
201
+ * safeExecSync('echo', [process.env.HOME || '']);
202
+ * ```
203
+ *
204
+ */
205
+ export declare function safeExecFromString(commandString: string, options?: SafeExecOptions): Buffer | string;
206
+ //# sourceMappingURL=safe-exec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-exec.d.ts","sourceRoot":"","sources":["../src/safe-exec.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC;IACjE,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,qBAAqB;IACrB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxC,SAAgB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;gBAGtC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,MAAM,EAAE,MAAM,GAAG,MAAM;CAQ1B;AA4DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,EAAO,EACnB,OAAO,GAAE,eAAoB,GAC5B,MAAM,GAAG,MAAM,CAsCjB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,EAAO,EACnB,OAAO,GAAE,eAAoB,GAC5B,cAAc,CA0ChB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,GAAE,MAAoB,GAC/B,MAAM,GAAG,IAAI,CAUf;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG;IACrD,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CA0CA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,eAAoB,GAC5B,MAAM,GAAG,MAAM,CAyBjB"}