@fredlackey/devutils 0.0.16 → 0.0.18
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/package.json +1 -1
- package/src/commands/setup.js +42 -8
- package/src/installs/homebrew.js +31 -36
- package/src/utils/common/shell.js +46 -1
package/package.json
CHANGED
package/src/commands/setup.js
CHANGED
|
@@ -11,8 +11,8 @@ const readline = require('readline');
|
|
|
11
11
|
const shell = require('../utils/common/shell');
|
|
12
12
|
const osUtils = require('../utils/common/os');
|
|
13
13
|
|
|
14
|
-
// Essential tools that DevUtils CLI requires
|
|
15
|
-
const
|
|
14
|
+
// Essential tools that DevUtils CLI requires (shared across all platforms)
|
|
15
|
+
const CORE_TOOLS = [
|
|
16
16
|
{
|
|
17
17
|
name: 'git',
|
|
18
18
|
command: 'git',
|
|
@@ -39,6 +39,31 @@ const ESSENTIAL_TOOLS = [
|
|
|
39
39
|
}
|
|
40
40
|
];
|
|
41
41
|
|
|
42
|
+
// Homebrew is required on macOS before installing other tools
|
|
43
|
+
const HOMEBREW_TOOL = {
|
|
44
|
+
name: 'brew',
|
|
45
|
+
command: 'brew',
|
|
46
|
+
description: 'Package manager (required)',
|
|
47
|
+
install: 'homebrew'
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the list of essential tools for the current platform.
|
|
52
|
+
* On macOS, Homebrew is included first since other tools depend on it.
|
|
53
|
+
* @returns {Array<object>} Array of tool definitions
|
|
54
|
+
*/
|
|
55
|
+
function getEssentialTools() {
|
|
56
|
+
const platform = osUtils.detect();
|
|
57
|
+
|
|
58
|
+
// On macOS, Homebrew must be installed first since all other tools use it
|
|
59
|
+
if (platform.type === 'macos') {
|
|
60
|
+
return [HOMEBREW_TOOL, ...CORE_TOOLS];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// On other platforms, just use the core tools
|
|
64
|
+
return CORE_TOOLS;
|
|
65
|
+
}
|
|
66
|
+
|
|
42
67
|
/**
|
|
43
68
|
* Create readline interface for prompts
|
|
44
69
|
* @returns {readline.Interface}
|
|
@@ -69,8 +94,9 @@ function confirm(rl, question) {
|
|
|
69
94
|
* @returns {Array<object>} Array of missing tools
|
|
70
95
|
*/
|
|
71
96
|
function checkMissingTools() {
|
|
97
|
+
const tools = getEssentialTools();
|
|
72
98
|
const missing = [];
|
|
73
|
-
for (const tool of
|
|
99
|
+
for (const tool of tools) {
|
|
74
100
|
if (!shell.commandExists(tool.command)) {
|
|
75
101
|
missing.push(tool);
|
|
76
102
|
}
|
|
@@ -83,7 +109,8 @@ function checkMissingTools() {
|
|
|
83
109
|
* @returns {Array<{ tool: object, installed: boolean }>}
|
|
84
110
|
*/
|
|
85
111
|
function getToolStatuses() {
|
|
86
|
-
|
|
112
|
+
const tools = getEssentialTools();
|
|
113
|
+
return tools.map(tool => ({
|
|
87
114
|
tool,
|
|
88
115
|
installed: shell.commandExists(tool.command)
|
|
89
116
|
}));
|
|
@@ -97,11 +124,18 @@ function getToolStatuses() {
|
|
|
97
124
|
async function installTool(tool) {
|
|
98
125
|
try {
|
|
99
126
|
const installer = require(`../installs/${tool.install}`);
|
|
100
|
-
if (typeof installer.install
|
|
101
|
-
|
|
127
|
+
if (typeof installer.install !== 'function') {
|
|
128
|
+
console.error(` No install function found for ${tool.name}`);
|
|
129
|
+
return false;
|
|
102
130
|
}
|
|
103
|
-
|
|
104
|
-
|
|
131
|
+
|
|
132
|
+
// Run the installer
|
|
133
|
+
await installer.install();
|
|
134
|
+
|
|
135
|
+
// Verify installation by checking if the command now exists
|
|
136
|
+
// This is more reliable than trusting return values from installers
|
|
137
|
+
const isNowInstalled = shell.commandExists(tool.command);
|
|
138
|
+
return isNowInstalled;
|
|
105
139
|
} catch (err) {
|
|
106
140
|
console.error(` Failed to install ${tool.name}: ${err.message}`);
|
|
107
141
|
return false;
|
package/src/installs/homebrew.js
CHANGED
|
@@ -277,21 +277,20 @@ async function install_macos() {
|
|
|
277
277
|
console.log(' - Xcode Command Line Tools (if not already installed)');
|
|
278
278
|
console.log('');
|
|
279
279
|
|
|
280
|
-
// Run the official Homebrew installer
|
|
281
|
-
//
|
|
280
|
+
// Run the official Homebrew installer with interactive terminal support
|
|
281
|
+
// This allows the installer to prompt for sudo password when needed
|
|
282
282
|
console.log('Downloading and running the Homebrew installer...');
|
|
283
283
|
console.log('This may take several minutes...');
|
|
284
|
+
console.log('You may be prompted for your password to complete the installation.');
|
|
284
285
|
console.log('');
|
|
285
286
|
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
{ timeout: 600000 } // 10 minute timeout for slow connections
|
|
287
|
+
const exitCode = await shell.spawnInteractive(
|
|
288
|
+
`/bin/bash -c "$(curl -fsSL ${HOMEBREW_INSTALL_URL})"`
|
|
289
289
|
);
|
|
290
290
|
|
|
291
|
-
if (
|
|
291
|
+
if (exitCode !== 0) {
|
|
292
292
|
throw new Error(
|
|
293
|
-
`Failed to install Homebrew.\n` +
|
|
294
|
-
`Output: ${installResult.stderr || installResult.stdout}\n\n` +
|
|
293
|
+
`Failed to install Homebrew (exit code: ${exitCode}).\n\n` +
|
|
295
294
|
`Troubleshooting:\n` +
|
|
296
295
|
` 1. If Xcode Command Line Tools installation hung, run:\n` +
|
|
297
296
|
` xcode-select --install\n` +
|
|
@@ -419,18 +418,17 @@ async function install_ubuntu() {
|
|
|
419
418
|
console.log('');
|
|
420
419
|
console.log('Downloading and running the Homebrew installer...');
|
|
421
420
|
console.log('This may take several minutes...');
|
|
421
|
+
console.log('You may be prompted for your password to complete the installation.');
|
|
422
422
|
console.log('');
|
|
423
423
|
|
|
424
|
-
// Run the official Homebrew installer
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
{ timeout: 600000 } // 10 minute timeout
|
|
424
|
+
// Run the official Homebrew installer with interactive terminal support
|
|
425
|
+
const exitCode = await shell.spawnInteractive(
|
|
426
|
+
`/bin/bash -c "$(curl -fsSL ${HOMEBREW_INSTALL_URL})"`
|
|
428
427
|
);
|
|
429
428
|
|
|
430
|
-
if (
|
|
429
|
+
if (exitCode !== 0) {
|
|
431
430
|
throw new Error(
|
|
432
|
-
`Failed to install Homebrew.\n` +
|
|
433
|
-
`Output: ${installResult.stderr || installResult.stdout}\n\n` +
|
|
431
|
+
`Failed to install Homebrew (exit code: ${exitCode}).\n\n` +
|
|
434
432
|
`Troubleshooting:\n` +
|
|
435
433
|
` 1. Ensure all dependencies are installed:\n` +
|
|
436
434
|
` sudo apt-get install -y build-essential procps curl file git\n` +
|
|
@@ -548,18 +546,17 @@ async function install_raspbian() {
|
|
|
548
546
|
console.log('');
|
|
549
547
|
console.log('Downloading and running the Homebrew installer...');
|
|
550
548
|
console.log('This may take several minutes (or longer on slower hardware)...');
|
|
549
|
+
console.log('You may be prompted for your password to complete the installation.');
|
|
551
550
|
console.log('');
|
|
552
551
|
|
|
553
|
-
// Run the official Homebrew installer
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
{ timeout: 1200000 } // 20 minute timeout for slower Raspberry Pi hardware
|
|
552
|
+
// Run the official Homebrew installer with interactive terminal support
|
|
553
|
+
const exitCode = await shell.spawnInteractive(
|
|
554
|
+
`/bin/bash -c "$(curl -fsSL ${HOMEBREW_INSTALL_URL})"`
|
|
557
555
|
);
|
|
558
556
|
|
|
559
|
-
if (
|
|
557
|
+
if (exitCode !== 0) {
|
|
560
558
|
throw new Error(
|
|
561
|
-
`Failed to install Homebrew.\n` +
|
|
562
|
-
`Output: ${installResult.stderr || installResult.stdout}\n\n` +
|
|
559
|
+
`Failed to install Homebrew (exit code: ${exitCode}).\n\n` +
|
|
563
560
|
`Troubleshooting:\n` +
|
|
564
561
|
` 1. Ensure all dependencies are installed:\n` +
|
|
565
562
|
` sudo apt-get install -y build-essential procps curl file git\n` +
|
|
@@ -706,18 +703,17 @@ async function install_amazon_linux() {
|
|
|
706
703
|
console.log('');
|
|
707
704
|
console.log('Downloading and running the Homebrew installer...');
|
|
708
705
|
console.log('This may take several minutes...');
|
|
706
|
+
console.log('You may be prompted for your password to complete the installation.');
|
|
709
707
|
console.log('');
|
|
710
708
|
|
|
711
|
-
// Run the official Homebrew installer
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
{ timeout: 600000 } // 10 minute timeout
|
|
709
|
+
// Run the official Homebrew installer with interactive terminal support
|
|
710
|
+
const exitCode = await shell.spawnInteractive(
|
|
711
|
+
`/bin/bash -c "$(curl -fsSL ${HOMEBREW_INSTALL_URL})"`
|
|
715
712
|
);
|
|
716
713
|
|
|
717
|
-
if (
|
|
714
|
+
if (exitCode !== 0) {
|
|
718
715
|
throw new Error(
|
|
719
|
-
`Failed to install Homebrew.\n` +
|
|
720
|
-
`Output: ${installResult.stderr || installResult.stdout}\n\n` +
|
|
716
|
+
`Failed to install Homebrew (exit code: ${exitCode}).\n\n` +
|
|
721
717
|
`Troubleshooting:\n` +
|
|
722
718
|
` 1. Ensure Development Tools are installed:\n` +
|
|
723
719
|
` sudo dnf groupinstall -y "Development Tools" # Amazon Linux 2023\n` +
|
|
@@ -835,18 +831,17 @@ async function install_ubuntu_wsl() {
|
|
|
835
831
|
console.log('');
|
|
836
832
|
console.log('Downloading and running the Homebrew installer...');
|
|
837
833
|
console.log('This may take several minutes...');
|
|
834
|
+
console.log('You may be prompted for your password to complete the installation.');
|
|
838
835
|
console.log('');
|
|
839
836
|
|
|
840
|
-
// Run the official Homebrew installer
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
{ timeout: 600000 } // 10 minute timeout
|
|
837
|
+
// Run the official Homebrew installer with interactive terminal support
|
|
838
|
+
const exitCode = await shell.spawnInteractive(
|
|
839
|
+
`/bin/bash -c "$(curl -fsSL ${HOMEBREW_INSTALL_URL})"`
|
|
844
840
|
);
|
|
845
841
|
|
|
846
|
-
if (
|
|
842
|
+
if (exitCode !== 0) {
|
|
847
843
|
throw new Error(
|
|
848
|
-
`Failed to install Homebrew in WSL.\n` +
|
|
849
|
-
`Output: ${installResult.stderr || installResult.stdout}\n\n` +
|
|
844
|
+
`Failed to install Homebrew in WSL (exit code: ${exitCode}).\n\n` +
|
|
850
845
|
`Troubleshooting:\n` +
|
|
851
846
|
` 1. Ensure WSL 2 is being used (WSL 1 has known issues):\n` +
|
|
852
847
|
` Run in PowerShell: wsl --set-version Ubuntu 2\n` +
|
|
@@ -207,10 +207,55 @@ async function spawnAsync(command, args = [], options = {}) {
|
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Spawns an interactive command with terminal passthrough.
|
|
212
|
+
*
|
|
213
|
+
* Use this function for commands that need user interaction, such as:
|
|
214
|
+
* - Commands that prompt for sudo password
|
|
215
|
+
* - Interactive installers that ask questions
|
|
216
|
+
* - Commands that show progress with cursor movement
|
|
217
|
+
*
|
|
218
|
+
* Unlike exec() or spawnAsync(), this function connects the child process
|
|
219
|
+
* directly to the parent's terminal (stdin, stdout, stderr), allowing the
|
|
220
|
+
* user to interact with the command as if they ran it directly.
|
|
221
|
+
*
|
|
222
|
+
* @param {string} command - The command to run (passed to shell)
|
|
223
|
+
* @param {Object} [options] - Options
|
|
224
|
+
* @param {string} [options.cwd] - Working directory for the command
|
|
225
|
+
* @param {Object} [options.env] - Environment variables (defaults to process.env)
|
|
226
|
+
* @returns {Promise<number>} Exit code (0 = success, non-zero = failure)
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* // Run an installer that needs sudo password
|
|
230
|
+
* const exitCode = await spawnInteractive('/bin/bash -c "$(curl -fsSL https://example.com/install.sh)"');
|
|
231
|
+
* if (exitCode !== 0) {
|
|
232
|
+
* console.error('Installation failed');
|
|
233
|
+
* }
|
|
234
|
+
*/
|
|
235
|
+
async function spawnInteractive(command, options = {}) {
|
|
236
|
+
return new Promise((resolve) => {
|
|
237
|
+
const child = spawn(command, [], {
|
|
238
|
+
stdio: 'inherit', // Connect to parent's terminal
|
|
239
|
+
shell: true, // Run through shell to support complex commands
|
|
240
|
+
cwd: options.cwd,
|
|
241
|
+
env: options.env || process.env
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
child.on('close', (code) => {
|
|
245
|
+
resolve(code || 0);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
child.on('error', () => {
|
|
249
|
+
resolve(1);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
210
254
|
module.exports = {
|
|
211
255
|
exec,
|
|
212
256
|
execSync,
|
|
213
257
|
which,
|
|
214
258
|
commandExists,
|
|
215
|
-
spawnAsync
|
|
259
|
+
spawnAsync,
|
|
260
|
+
spawnInteractive
|
|
216
261
|
};
|