@fredlackey/devutils 0.0.1

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 (200) hide show
  1. package/README.md +156 -0
  2. package/bin/dev.js +16 -0
  3. package/files/README.md +0 -0
  4. package/files/claude/.claude/commands/setup-context.md +3 -0
  5. package/files/monorepos/_archive/README.md +36 -0
  6. package/files/monorepos/_legacy/README.md +36 -0
  7. package/files/monorepos/ai-docs/README.md +33 -0
  8. package/files/monorepos/apps/README.md +24 -0
  9. package/files/monorepos/docs/README.md +40 -0
  10. package/files/monorepos/packages/README.md +25 -0
  11. package/files/monorepos/research/README.md +29 -0
  12. package/files/monorepos/scripts/README.md +24 -0
  13. package/package.json +39 -0
  14. package/src/cli.js +68 -0
  15. package/src/commands/README.md +41 -0
  16. package/src/commands/configure.js +199 -0
  17. package/src/commands/identity.js +1630 -0
  18. package/src/commands/ignore.js +247 -0
  19. package/src/commands/install.js +173 -0
  20. package/src/commands/setup.js +212 -0
  21. package/src/commands/status.js +223 -0
  22. package/src/completion.js +284 -0
  23. package/src/constants.js +45 -0
  24. package/src/ignore/claude-code.txt +10 -0
  25. package/src/ignore/docker.txt +18 -0
  26. package/src/ignore/linux.txt +23 -0
  27. package/src/ignore/macos.txt +36 -0
  28. package/src/ignore/node.txt +55 -0
  29. package/src/ignore/terraform.txt +37 -0
  30. package/src/ignore/vscode.txt +18 -0
  31. package/src/ignore/windows.txt +35 -0
  32. package/src/index.js +0 -0
  33. package/src/installs/README.md +399 -0
  34. package/src/installs/adobe-creative-cloud.js +44 -0
  35. package/src/installs/appcleaner.js +44 -0
  36. package/src/installs/atomicparsley.js +44 -0
  37. package/src/installs/aws-cli.js +44 -0
  38. package/src/installs/balena-etcher.js +44 -0
  39. package/src/installs/bambu-studio.js +44 -0
  40. package/src/installs/bash-completion.js +44 -0
  41. package/src/installs/bash.js +44 -0
  42. package/src/installs/beyond-compare.js +44 -0
  43. package/src/installs/build-essential.js +44 -0
  44. package/src/installs/caffeine.js +44 -0
  45. package/src/installs/camtasia.js +44 -0
  46. package/src/installs/chatgpt.js +44 -0
  47. package/src/installs/chrome-canary.js +44 -0
  48. package/src/installs/chromium.js +44 -0
  49. package/src/installs/claude-code.js +44 -0
  50. package/src/installs/curl.js +44 -0
  51. package/src/installs/cursor.js +44 -0
  52. package/src/installs/dbschema.js +44 -0
  53. package/src/installs/docker.js +44 -0
  54. package/src/installs/drawio.js +44 -0
  55. package/src/installs/elmedia-player.js +44 -0
  56. package/src/installs/ffmpeg.js +44 -0
  57. package/src/installs/gemini-cli.js +44 -0
  58. package/src/installs/git.js +44 -0
  59. package/src/installs/gitego.js +44 -0
  60. package/src/installs/go.js +44 -0
  61. package/src/installs/google-chrome.js +44 -0
  62. package/src/installs/gpg.js +141 -0
  63. package/src/installs/homebrew.js +44 -0
  64. package/src/installs/imageoptim.js +44 -0
  65. package/src/installs/jq.js +44 -0
  66. package/src/installs/keyboard-maestro.js +44 -0
  67. package/src/installs/latex.js +44 -0
  68. package/src/installs/lftp.js +44 -0
  69. package/src/installs/messenger.js +44 -0
  70. package/src/installs/microsoft-office.js +44 -0
  71. package/src/installs/microsoft-teams.js +44 -0
  72. package/src/installs/node.js +44 -0
  73. package/src/installs/nordpass.js +44 -0
  74. package/src/installs/nvm.js +44 -0
  75. package/src/installs/openssh.js +134 -0
  76. package/src/installs/pandoc.js +44 -0
  77. package/src/installs/pinentry.js +44 -0
  78. package/src/installs/pngyu.js +44 -0
  79. package/src/installs/postman.js +44 -0
  80. package/src/installs/safari-tech-preview.js +44 -0
  81. package/src/installs/sfnt2woff.js +44 -0
  82. package/src/installs/shellcheck.js +44 -0
  83. package/src/installs/slack.js +44 -0
  84. package/src/installs/snagit.js +44 -0
  85. package/src/installs/spotify.js +44 -0
  86. package/src/installs/studio-3t.js +44 -0
  87. package/src/installs/sublime-text.js +44 -0
  88. package/src/installs/superwhisper.js +44 -0
  89. package/src/installs/tailscale.js +44 -0
  90. package/src/installs/termius.js +44 -0
  91. package/src/installs/terraform.js +44 -0
  92. package/src/installs/tidal.js +44 -0
  93. package/src/installs/tmux.js +44 -0
  94. package/src/installs/tree.js +44 -0
  95. package/src/installs/vim.js +44 -0
  96. package/src/installs/vlc.js +44 -0
  97. package/src/installs/vscode.js +44 -0
  98. package/src/installs/whatsapp.js +44 -0
  99. package/src/installs/woff2.js +44 -0
  100. package/src/installs/xcode.js +44 -0
  101. package/src/installs/yarn.js +44 -0
  102. package/src/installs/yq.js +44 -0
  103. package/src/installs/yt-dlp.js +44 -0
  104. package/src/installs/zoom.js +44 -0
  105. package/src/scripts/README.md +95 -0
  106. package/src/scripts/afk.js +23 -0
  107. package/src/scripts/backup-all.js +24 -0
  108. package/src/scripts/backup-source.js +24 -0
  109. package/src/scripts/brewd.js +23 -0
  110. package/src/scripts/brewi.js +24 -0
  111. package/src/scripts/brewr.js +24 -0
  112. package/src/scripts/brews.js +24 -0
  113. package/src/scripts/brewu.js +23 -0
  114. package/src/scripts/c.js +23 -0
  115. package/src/scripts/ccurl.js +24 -0
  116. package/src/scripts/certbot-crontab-init.js +24 -0
  117. package/src/scripts/certbot-init.js +25 -0
  118. package/src/scripts/ch.js +23 -0
  119. package/src/scripts/claude-danger.js +23 -0
  120. package/src/scripts/clean-dev.js +24 -0
  121. package/src/scripts/clear-dns-cache.js +23 -0
  122. package/src/scripts/clone.js +25 -0
  123. package/src/scripts/code-all.js +24 -0
  124. package/src/scripts/count-files.js +24 -0
  125. package/src/scripts/count-folders.js +24 -0
  126. package/src/scripts/count.js +24 -0
  127. package/src/scripts/d.js +23 -0
  128. package/src/scripts/datauri.js +24 -0
  129. package/src/scripts/delete-files.js +24 -0
  130. package/src/scripts/docker-clean.js +24 -0
  131. package/src/scripts/dp.js +23 -0
  132. package/src/scripts/e.js +24 -0
  133. package/src/scripts/empty-trash.js +23 -0
  134. package/src/scripts/evm.js +25 -0
  135. package/src/scripts/fetch-github-repos.js +25 -0
  136. package/src/scripts/get-channel.js +24 -0
  137. package/src/scripts/get-course.js +26 -0
  138. package/src/scripts/get-dependencies.js +25 -0
  139. package/src/scripts/get-folder.js +26 -0
  140. package/src/scripts/get-tunes.js +25 -0
  141. package/src/scripts/get-video.js +24 -0
  142. package/src/scripts/git-backup.js +25 -0
  143. package/src/scripts/git-clone.js +25 -0
  144. package/src/scripts/git-pup.js +23 -0
  145. package/src/scripts/git-push.js +24 -0
  146. package/src/scripts/h.js +24 -0
  147. package/src/scripts/hide-desktop-icons.js +23 -0
  148. package/src/scripts/hide-hidden-files.js +23 -0
  149. package/src/scripts/install-dependencies-from.js +25 -0
  150. package/src/scripts/ips.js +26 -0
  151. package/src/scripts/iso.js +24 -0
  152. package/src/scripts/killni.js +23 -0
  153. package/src/scripts/ll.js +24 -0
  154. package/src/scripts/local-ip.js +23 -0
  155. package/src/scripts/m.js +24 -0
  156. package/src/scripts/map.js +24 -0
  157. package/src/scripts/mkd.js +24 -0
  158. package/src/scripts/ncu-update-all.js +24 -0
  159. package/src/scripts/nginx-init.js +28 -0
  160. package/src/scripts/npmi.js +23 -0
  161. package/src/scripts/o.js +24 -0
  162. package/src/scripts/org-by-date.js +24 -0
  163. package/src/scripts/p.js +23 -0
  164. package/src/scripts/packages.js +25 -0
  165. package/src/scripts/path.js +23 -0
  166. package/src/scripts/ports.js +23 -0
  167. package/src/scripts/q.js +23 -0
  168. package/src/scripts/refresh-files.js +26 -0
  169. package/src/scripts/remove-smaller-files.js +24 -0
  170. package/src/scripts/rename-files-with-date.js +25 -0
  171. package/src/scripts/resize-image.js +25 -0
  172. package/src/scripts/rm-safe.js +24 -0
  173. package/src/scripts/s.js +24 -0
  174. package/src/scripts/set-git-public.js +23 -0
  175. package/src/scripts/show-desktop-icons.js +23 -0
  176. package/src/scripts/show-hidden-files.js +23 -0
  177. package/src/scripts/tpa.js +23 -0
  178. package/src/scripts/tpo.js +23 -0
  179. package/src/scripts/u.js +23 -0
  180. package/src/scripts/vpush.js +23 -0
  181. package/src/scripts/y.js +23 -0
  182. package/src/utils/README.md +95 -0
  183. package/src/utils/common/apps.js +143 -0
  184. package/src/utils/common/display.js +157 -0
  185. package/src/utils/common/network.js +185 -0
  186. package/src/utils/common/os.js +202 -0
  187. package/src/utils/common/package-manager.js +301 -0
  188. package/src/utils/common/privileges.js +138 -0
  189. package/src/utils/common/shell.js +195 -0
  190. package/src/utils/macos/apps.js +228 -0
  191. package/src/utils/macos/brew.js +315 -0
  192. package/src/utils/ubuntu/apt.js +301 -0
  193. package/src/utils/ubuntu/desktop.js +292 -0
  194. package/src/utils/ubuntu/snap.js +302 -0
  195. package/src/utils/ubuntu/systemd.js +286 -0
  196. package/src/utils/windows/choco.js +327 -0
  197. package/src/utils/windows/env.js +246 -0
  198. package/src/utils/windows/registry.js +269 -0
  199. package/src/utils/windows/shell.js +240 -0
  200. package/src/utils/windows/winget.js +378 -0
@@ -0,0 +1,301 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Package Manager Abstraction Utilities
5
+ *
6
+ * Cross-platform abstraction layer for package managers.
7
+ * Provides a unified interface for installing packages across different platforms.
8
+ */
9
+
10
+ const osUtils = require('./os');
11
+ const shell = require('./shell');
12
+
13
+ /**
14
+ * Returns list of available package managers on current system
15
+ * @returns {Promise<string[]>}
16
+ */
17
+ async function getAvailable() {
18
+ const available = [];
19
+ const platform = osUtils.detect();
20
+
21
+ switch (platform.type) {
22
+ case 'macos':
23
+ if (shell.commandExists('brew')) {
24
+ available.push('brew');
25
+ }
26
+ break;
27
+
28
+ case 'ubuntu':
29
+ case 'debian':
30
+ case 'raspbian':
31
+ if (shell.commandExists('apt-get')) {
32
+ available.push('apt');
33
+ }
34
+ if (shell.commandExists('snap')) {
35
+ available.push('snap');
36
+ }
37
+ break;
38
+
39
+ case 'amazon_linux':
40
+ case 'rhel':
41
+ case 'fedora':
42
+ if (shell.commandExists('dnf')) {
43
+ available.push('dnf');
44
+ }
45
+ if (shell.commandExists('yum')) {
46
+ available.push('yum');
47
+ }
48
+ break;
49
+
50
+ case 'windows':
51
+ if (shell.commandExists('winget')) {
52
+ available.push('winget');
53
+ }
54
+ if (shell.commandExists('choco')) {
55
+ available.push('choco');
56
+ }
57
+ break;
58
+ }
59
+
60
+ // Universal package managers
61
+ if (shell.commandExists('npm')) {
62
+ available.push('npm');
63
+ }
64
+ if (shell.commandExists('pip') || shell.commandExists('pip3')) {
65
+ available.push('pip');
66
+ }
67
+
68
+ return available;
69
+ }
70
+
71
+ /**
72
+ * Returns the preferred/recommended package manager for current platform
73
+ * @returns {string|null}
74
+ */
75
+ function getPreferred() {
76
+ const platform = osUtils.detect();
77
+
78
+ switch (platform.type) {
79
+ case 'macos':
80
+ return 'brew';
81
+ case 'ubuntu':
82
+ case 'debian':
83
+ case 'raspbian':
84
+ return 'apt';
85
+ case 'amazon_linux':
86
+ case 'rhel':
87
+ case 'fedora':
88
+ return platform.packageManager; // dnf or yum
89
+ case 'windows':
90
+ return shell.commandExists('winget') ? 'winget' : 'choco';
91
+ default:
92
+ return null;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Installs a package using the best available package manager
98
+ * @param {string} packageName - The package to install
99
+ * @param {Object} [options] - Installation options
100
+ * @param {string} [options.manager] - Force a specific package manager
101
+ * @param {boolean} [options.global] - Install globally (for npm/pip)
102
+ * @param {boolean} [options.cask] - Install as cask (macOS only)
103
+ * @param {boolean} [options.classic] - Use classic confinement (snap only)
104
+ * @returns {Promise<{ success: boolean, output: string }>}
105
+ */
106
+ async function install(packageName, options = {}) {
107
+ const manager = options.manager || getPreferred();
108
+
109
+ if (!manager) {
110
+ return {
111
+ success: false,
112
+ output: 'No package manager available'
113
+ };
114
+ }
115
+
116
+ let command;
117
+
118
+ switch (manager) {
119
+ case 'brew':
120
+ if (options.cask) {
121
+ command = `brew install --cask ${packageName}`;
122
+ } else {
123
+ command = `brew install ${packageName}`;
124
+ }
125
+ break;
126
+
127
+ case 'apt':
128
+ command = `sudo apt-get install -y ${packageName}`;
129
+ break;
130
+
131
+ case 'snap':
132
+ if (options.classic) {
133
+ command = `sudo snap install ${packageName} --classic`;
134
+ } else {
135
+ command = `sudo snap install ${packageName}`;
136
+ }
137
+ break;
138
+
139
+ case 'dnf':
140
+ command = `sudo dnf install -y ${packageName}`;
141
+ break;
142
+
143
+ case 'yum':
144
+ command = `sudo yum install -y ${packageName}`;
145
+ break;
146
+
147
+ case 'winget':
148
+ command = `winget install --accept-package-agreements --accept-source-agreements ${packageName}`;
149
+ break;
150
+
151
+ case 'choco':
152
+ command = `choco install -y ${packageName}`;
153
+ break;
154
+
155
+ case 'npm':
156
+ if (options.global) {
157
+ command = `npm install -g ${packageName}`;
158
+ } else {
159
+ command = `npm install ${packageName}`;
160
+ }
161
+ break;
162
+
163
+ case 'pip':
164
+ const pip = shell.commandExists('pip3') ? 'pip3' : 'pip';
165
+ if (options.global) {
166
+ command = `${pip} install ${packageName}`;
167
+ } else {
168
+ command = `${pip} install --user ${packageName}`;
169
+ }
170
+ break;
171
+
172
+ default:
173
+ return {
174
+ success: false,
175
+ output: `Unknown package manager: ${manager}`
176
+ };
177
+ }
178
+
179
+ const result = await shell.exec(command);
180
+ return {
181
+ success: result.code === 0,
182
+ output: result.stdout || result.stderr
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Uninstalls a package
188
+ * @param {string} packageName - The package to uninstall
189
+ * @param {Object} [options] - Uninstallation options
190
+ * @param {string} [options.manager] - Force a specific package manager
191
+ * @returns {Promise<{ success: boolean, output: string }>}
192
+ */
193
+ async function uninstall(packageName, options = {}) {
194
+ const manager = options.manager || getPreferred();
195
+
196
+ if (!manager) {
197
+ return {
198
+ success: false,
199
+ output: 'No package manager available'
200
+ };
201
+ }
202
+
203
+ let command;
204
+
205
+ switch (manager) {
206
+ case 'brew':
207
+ command = `brew uninstall ${packageName}`;
208
+ break;
209
+
210
+ case 'apt':
211
+ command = `sudo apt-get remove -y ${packageName}`;
212
+ break;
213
+
214
+ case 'snap':
215
+ command = `sudo snap remove ${packageName}`;
216
+ break;
217
+
218
+ case 'dnf':
219
+ command = `sudo dnf remove -y ${packageName}`;
220
+ break;
221
+
222
+ case 'yum':
223
+ command = `sudo yum remove -y ${packageName}`;
224
+ break;
225
+
226
+ case 'winget':
227
+ command = `winget uninstall ${packageName}`;
228
+ break;
229
+
230
+ case 'choco':
231
+ command = `choco uninstall -y ${packageName}`;
232
+ break;
233
+
234
+ default:
235
+ return {
236
+ success: false,
237
+ output: `Unknown package manager: ${manager}`
238
+ };
239
+ }
240
+
241
+ const result = await shell.exec(command);
242
+ return {
243
+ success: result.code === 0,
244
+ output: result.stdout || result.stderr
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Updates package lists
250
+ * @param {string} [manager] - Specific package manager to update
251
+ * @returns {Promise<{ success: boolean, output: string }>}
252
+ */
253
+ async function update(manager) {
254
+ const pm = manager || getPreferred();
255
+
256
+ let command;
257
+
258
+ switch (pm) {
259
+ case 'brew':
260
+ command = 'brew update';
261
+ break;
262
+
263
+ case 'apt':
264
+ command = 'sudo apt-get update';
265
+ break;
266
+
267
+ case 'dnf':
268
+ command = 'sudo dnf check-update';
269
+ break;
270
+
271
+ case 'yum':
272
+ command = 'sudo yum check-update';
273
+ break;
274
+
275
+ case 'choco':
276
+ command = 'choco upgrade chocolatey -y';
277
+ break;
278
+
279
+ default:
280
+ return {
281
+ success: false,
282
+ output: `Update not supported for: ${pm}`
283
+ };
284
+ }
285
+
286
+ const result = await shell.exec(command);
287
+ // dnf/yum check-update returns 100 if updates are available, not an error
288
+ const success = result.code === 0 || (pm === 'dnf' || pm === 'yum') && result.code === 100;
289
+ return {
290
+ success,
291
+ output: result.stdout || result.stderr
292
+ };
293
+ }
294
+
295
+ module.exports = {
296
+ getAvailable,
297
+ getPreferred,
298
+ install,
299
+ uninstall,
300
+ update
301
+ };
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Permission and Elevation Checking Utilities
5
+ *
6
+ * Platform-agnostic utilities for checking elevated privileges.
7
+ * Handles root on Unix and Administrator on Windows internally.
8
+ */
9
+
10
+ const os = require('os');
11
+
12
+ /**
13
+ * Checks if running with elevated privileges
14
+ * On Unix: checks if running as root (uid 0)
15
+ * On Windows: checks if running as Administrator
16
+ * @returns {boolean}
17
+ */
18
+ function isElevated() {
19
+ if (process.platform === 'win32') {
20
+ return isWindowsAdmin();
21
+ }
22
+ return isUnixRoot();
23
+ }
24
+
25
+ /**
26
+ * Checks if running as root on Unix systems
27
+ * @returns {boolean}
28
+ */
29
+ function isUnixRoot() {
30
+ if (process.platform === 'win32') {
31
+ return false;
32
+ }
33
+ return process.getuid && process.getuid() === 0;
34
+ }
35
+
36
+ /**
37
+ * Checks if running as Administrator on Windows
38
+ * Uses a heuristic since Node.js doesn't have direct API for this
39
+ * @returns {boolean}
40
+ */
41
+ function isWindowsAdmin() {
42
+ if (process.platform !== 'win32') {
43
+ return false;
44
+ }
45
+
46
+ // Method 1: Try to read a system-protected location
47
+ const fs = require('fs');
48
+ try {
49
+ // Try to list the Windows system config directory
50
+ // Only administrators can access this
51
+ fs.readdirSync('C:\\Windows\\System32\\config');
52
+ return true;
53
+ } catch {
54
+ // Not admin or access denied
55
+ }
56
+
57
+ // Method 2: Check for common admin environment indicators
58
+ // This is less reliable but can be a fallback
59
+ if (process.env.ADMIN_MODE === 'true') {
60
+ return true;
61
+ }
62
+
63
+ return false;
64
+ }
65
+
66
+ /**
67
+ * Determines if a given operation type requires elevation
68
+ * @param {string} operation - The operation type
69
+ * @returns {boolean}
70
+ */
71
+ function requiresElevation(operation) {
72
+ const elevatedOperations = [
73
+ 'install_system_package',
74
+ 'modify_system_config',
75
+ 'start_system_service',
76
+ 'modify_hosts_file',
77
+ 'install_global_npm',
78
+ 'modify_registry',
79
+ 'add_apt_repository',
80
+ 'enable_systemd_service'
81
+ ];
82
+
83
+ return elevatedOperations.includes(operation);
84
+ }
85
+
86
+ /**
87
+ * Gets the username of the current user
88
+ * @returns {string}
89
+ */
90
+ function getCurrentUser() {
91
+ return os.userInfo().username;
92
+ }
93
+
94
+ /**
95
+ * Checks if the current user can write to a directory
96
+ * @param {string} dirPath - The directory path to check
97
+ * @returns {boolean}
98
+ */
99
+ function canWriteToDirectory(dirPath) {
100
+ const fs = require('fs');
101
+ const path = require('path');
102
+
103
+ try {
104
+ // Try to create a temporary file
105
+ const testFile = path.join(dirPath, `.write-test-${Date.now()}`);
106
+ fs.writeFileSync(testFile, '');
107
+ fs.unlinkSync(testFile);
108
+ return true;
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Checks if the current user can execute a file
116
+ * @param {string} filePath - The file path to check
117
+ * @returns {boolean}
118
+ */
119
+ function canExecute(filePath) {
120
+ const fs = require('fs');
121
+
122
+ try {
123
+ fs.accessSync(filePath, fs.constants.X_OK);
124
+ return true;
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ module.exports = {
131
+ isElevated,
132
+ isUnixRoot,
133
+ isWindowsAdmin,
134
+ requiresElevation,
135
+ getCurrentUser,
136
+ canWriteToDirectory,
137
+ canExecute
138
+ };
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Shell Command Execution Utilities
5
+ *
6
+ * Platform-agnostic utilities for executing shell commands. Uses Node.js
7
+ * child_process APIs that work identically on all platforms. Note that while
8
+ * these utilities are cross-platform, the commands passed to them may be
9
+ * platform-specific.
10
+ */
11
+
12
+ const { exec: cpExec, execSync: cpExecSync, spawn } = require('child_process');
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ /**
17
+ * Executes a shell command asynchronously
18
+ * @param {string} command - The command to execute
19
+ * @param {Object} [options] - Options for child_process.exec
20
+ * @param {string} [options.cwd] - Working directory
21
+ * @param {Object} [options.env] - Environment variables
22
+ * @param {number} [options.timeout] - Timeout in milliseconds
23
+ * @param {string} [options.encoding] - Output encoding (default: 'utf8')
24
+ * @returns {Promise<{ stdout: string, stderr: string, code: number }>}
25
+ */
26
+ async function exec(command, options = {}) {
27
+ return new Promise((resolve, reject) => {
28
+ const opts = {
29
+ encoding: options.encoding || 'utf8',
30
+ cwd: options.cwd,
31
+ env: options.env || process.env,
32
+ timeout: options.timeout,
33
+ maxBuffer: options.maxBuffer || 10 * 1024 * 1024 // 10MB default
34
+ };
35
+
36
+ cpExec(command, opts, (error, stdout, stderr) => {
37
+ if (error) {
38
+ resolve({
39
+ stdout: stdout || '',
40
+ stderr: stderr || error.message,
41
+ code: error.code || 1
42
+ });
43
+ } else {
44
+ resolve({
45
+ stdout: stdout || '',
46
+ stderr: stderr || '',
47
+ code: 0
48
+ });
49
+ }
50
+ });
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Executes a shell command synchronously
56
+ * @param {string} command - The command to execute
57
+ * @param {Object} [options] - Options for child_process.execSync
58
+ * @param {string} [options.cwd] - Working directory
59
+ * @param {Object} [options.env] - Environment variables
60
+ * @param {number} [options.timeout] - Timeout in milliseconds
61
+ * @param {string} [options.encoding] - Output encoding (default: 'utf8')
62
+ * @returns {string} - stdout as string, or empty string on error
63
+ */
64
+ function execSync(command, options = {}) {
65
+ try {
66
+ const opts = {
67
+ encoding: options.encoding || 'utf8',
68
+ cwd: options.cwd,
69
+ env: options.env || process.env,
70
+ timeout: options.timeout,
71
+ maxBuffer: options.maxBuffer || 10 * 1024 * 1024, // 10MB default
72
+ stdio: ['pipe', 'pipe', 'pipe']
73
+ };
74
+
75
+ return cpExecSync(command, opts).toString().trim();
76
+ } catch (err) {
77
+ return '';
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Locates an executable in PATH (pure Node.js implementation)
83
+ * @param {string} executable - The executable name to find
84
+ * @returns {string|null} - Full path to executable, or null if not found
85
+ */
86
+ function which(executable) {
87
+ const isWindows = process.platform === 'win32';
88
+ const pathSeparator = isWindows ? ';' : ':';
89
+ const pathEnv = process.env.PATH || '';
90
+ const paths = pathEnv.split(pathSeparator);
91
+
92
+ // On Windows, also check PATHEXT for executable extensions
93
+ const pathExt = isWindows
94
+ ? (process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';')
95
+ : [''];
96
+
97
+ for (const dir of paths) {
98
+ if (!dir) continue;
99
+
100
+ for (const ext of pathExt) {
101
+ const fullPath = path.join(dir, executable + ext);
102
+ try {
103
+ const stat = fs.statSync(fullPath);
104
+ if (stat.isFile()) {
105
+ // On Unix, check if file is executable
106
+ if (!isWindows) {
107
+ try {
108
+ fs.accessSync(fullPath, fs.constants.X_OK);
109
+ return fullPath;
110
+ } catch {
111
+ continue;
112
+ }
113
+ }
114
+ return fullPath;
115
+ }
116
+ } catch {
117
+ continue;
118
+ }
119
+ }
120
+ }
121
+
122
+ return null;
123
+ }
124
+
125
+ /**
126
+ * Checks if a command is available in PATH
127
+ * @param {string} command - The command to check
128
+ * @returns {boolean}
129
+ */
130
+ function commandExists(command) {
131
+ return which(command) !== null;
132
+ }
133
+
134
+ /**
135
+ * Spawns a process with streaming output
136
+ * @param {string} command - The command to run
137
+ * @param {string[]} [args] - Arguments array
138
+ * @param {Object} [options] - Spawn options
139
+ * @returns {Promise<{ stdout: string, stderr: string, code: number }>}
140
+ */
141
+ async function spawnAsync(command, args = [], options = {}) {
142
+ return new Promise((resolve, reject) => {
143
+ const opts = {
144
+ cwd: options.cwd,
145
+ env: options.env || process.env,
146
+ shell: options.shell !== false
147
+ };
148
+
149
+ const child = spawn(command, args, opts);
150
+ let stdout = '';
151
+ let stderr = '';
152
+
153
+ if (child.stdout) {
154
+ child.stdout.on('data', (data) => {
155
+ stdout += data.toString();
156
+ if (options.onStdout) {
157
+ options.onStdout(data.toString());
158
+ }
159
+ });
160
+ }
161
+
162
+ if (child.stderr) {
163
+ child.stderr.on('data', (data) => {
164
+ stderr += data.toString();
165
+ if (options.onStderr) {
166
+ options.onStderr(data.toString());
167
+ }
168
+ });
169
+ }
170
+
171
+ child.on('close', (code) => {
172
+ resolve({
173
+ stdout,
174
+ stderr,
175
+ code: code || 0
176
+ });
177
+ });
178
+
179
+ child.on('error', (err) => {
180
+ resolve({
181
+ stdout,
182
+ stderr: stderr || err.message,
183
+ code: 1
184
+ });
185
+ });
186
+ });
187
+ }
188
+
189
+ module.exports = {
190
+ exec,
191
+ execSync,
192
+ which,
193
+ commandExists,
194
+ spawnAsync
195
+ };