auto-dev-setup 0.1.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.
@@ -0,0 +1,348 @@
1
+ /**
2
+ * macOS Platform Installer
3
+ * Handles macOS-specific installations
4
+ */
5
+
6
+ const { execCommand, execSilent, commandExists } = require('../utils/executor');
7
+ const logger = require('../utils/logger');
8
+ const ora = require('ora');
9
+
10
+ /**
11
+ * Install Xcode Command Line Tools
12
+ * @returns {Object} Installation result
13
+ */
14
+ async function installXcodeCliTools() {
15
+ logger.step('Checking Xcode Command Line Tools...');
16
+
17
+ // Check if already installed
18
+ const check = execSilent('xcode-select -p');
19
+ if (check.success) {
20
+ logger.success('Xcode Command Line Tools already installed');
21
+ return { success: true, skipped: true };
22
+ }
23
+
24
+ logger.step('Installing Xcode Command Line Tools...');
25
+ logger.info('This may take several minutes and require user interaction.');
26
+
27
+ const result = execCommand('xcode-select --install', { silent: false });
28
+
29
+ if (result.success) {
30
+ logger.success('Xcode Command Line Tools installed');
31
+ return { success: true, skipped: false };
32
+ } else {
33
+ logger.warn('Xcode CLI Tools installation started. Please complete the installation dialog.');
34
+ logger.info('Run this tool again after installation completes.');
35
+ return { success: false, skipped: false, pending: true };
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Install Homebrew
41
+ * @returns {Object} Installation result
42
+ */
43
+ async function installHomebrew() {
44
+ logger.step('Checking Homebrew...');
45
+
46
+ if (commandExists('brew')) {
47
+ logger.success('Homebrew already installed');
48
+
49
+ // Update Homebrew
50
+ logger.step('Updating Homebrew...');
51
+ execCommand('brew update', { silent: true });
52
+ return { success: true, skipped: true };
53
+ }
54
+
55
+ logger.step('Installing Homebrew...');
56
+ const spinner = ora('Downloading and installing Homebrew...').start();
57
+
58
+ const installScript =
59
+ '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"';
60
+
61
+ const result = execCommand(installScript);
62
+
63
+ if (result.success) {
64
+ spinner.succeed('Homebrew installed successfully');
65
+
66
+ // Add to PATH for Apple Silicon Macs
67
+ const arch = process.arch;
68
+ if (arch === 'arm64') {
69
+ logger.info('Configuring Homebrew for Apple Silicon...');
70
+ const shellConfig = process.env.SHELL?.includes('zsh')
71
+ ? '~/.zshrc'
72
+ : '~/.bash_profile';
73
+ execCommand(
74
+ `echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ${shellConfig}`
75
+ );
76
+ execCommand('eval "$(/opt/homebrew/bin/brew shellenv)"');
77
+ }
78
+
79
+ return { success: true, skipped: false };
80
+ } else {
81
+ spinner.fail('Failed to install Homebrew');
82
+ return { success: false, skipped: false };
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Install a package using Homebrew
88
+ * @param {string} packageName - Package to install
89
+ * @param {string} caskName - Cask name if it's a cask (optional)
90
+ * @returns {Object} Installation result
91
+ */
92
+ async function brewInstall(packageName, caskName = null) {
93
+ const isCask = caskName !== null;
94
+ const name = caskName || packageName;
95
+
96
+ logger.step(`Checking ${name}...`);
97
+
98
+ // Check if already installed
99
+ if (isCask) {
100
+ const checkResult = execSilent(`brew list --cask ${name} 2>/dev/null`);
101
+ if (checkResult.success) {
102
+ logger.success(`${name} already installed`);
103
+ return { success: true, skipped: true };
104
+ }
105
+ } else {
106
+ if (commandExists(packageName)) {
107
+ logger.success(`${name} already installed`);
108
+ return { success: true, skipped: true };
109
+ }
110
+ }
111
+
112
+ logger.step(`Installing ${name}...`);
113
+
114
+ const command = isCask
115
+ ? `brew install --cask ${name}`
116
+ : `brew install ${packageName}`;
117
+
118
+ const result = execCommand(command);
119
+
120
+ if (result.success) {
121
+ logger.success(`${name} installed successfully`);
122
+ return { success: true, skipped: false };
123
+ } else {
124
+ logger.error(`Failed to install ${name}`);
125
+ return { success: false, skipped: false };
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Install Git via Homebrew
131
+ */
132
+ async function installGit() {
133
+ return brewInstall('git');
134
+ }
135
+
136
+ /**
137
+ * Install curl via Homebrew
138
+ */
139
+ async function installCurl() {
140
+ return brewInstall('curl');
141
+ }
142
+
143
+ /**
144
+ * Install Node.js via Homebrew
145
+ */
146
+ async function installNode() {
147
+ return brewInstall('node');
148
+ }
149
+
150
+ /**
151
+ * Install Visual Studio Code via Homebrew Cask
152
+ */
153
+ async function installVSCode() {
154
+ return brewInstall('visual-studio-code', 'visual-studio-code');
155
+ }
156
+
157
+ /**
158
+ * Enable VS Code command line tool
159
+ */
160
+ async function enableVSCodeCli() {
161
+ logger.step('Enabling VS Code CLI (code command)...');
162
+
163
+ // On macOS, VS Code CLI should be available after cask install
164
+ // But we can manually add it to PATH if needed
165
+ if (commandExists('code')) {
166
+ logger.success('VS Code CLI already available');
167
+ return { success: true, skipped: true };
168
+ }
169
+
170
+ // Try to create symlink manually
171
+ const vscodePath =
172
+ '/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code';
173
+ const result = execCommand(
174
+ `ln -sf "${vscodePath}" /usr/local/bin/code 2>/dev/null || sudo ln -sf "${vscodePath}" /usr/local/bin/code`
175
+ );
176
+
177
+ if (result.success || commandExists('code')) {
178
+ logger.success('VS Code CLI enabled');
179
+ return { success: true, skipped: false };
180
+ } else {
181
+ logger.warn('Could not enable VS Code CLI automatically');
182
+ logger.info(
183
+ 'You can enable it manually: Open VS Code > Cmd+Shift+P > "Shell Command: Install code"'
184
+ );
185
+ return { success: false, skipped: false };
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Install Python via Homebrew
191
+ */
192
+ async function installPython() {
193
+ // Python is a formula, not a cask
194
+ logger.step('Checking Python...');
195
+
196
+ // Check if python3 is already available
197
+ if (commandExists('python3')) {
198
+ logger.success('Python already installed');
199
+ return { success: true, skipped: true };
200
+ }
201
+
202
+ logger.step('Installing Python...');
203
+ const result = execCommand('brew install python');
204
+
205
+ if (result.success) {
206
+ logger.success('Python installed successfully');
207
+ return { success: true, skipped: false };
208
+ } else {
209
+ logger.error('Failed to install Python');
210
+ return { success: false, skipped: false };
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Install Java (OpenJDK) via Homebrew
216
+ */
217
+ async function installJava() {
218
+ // OpenJDK is a formula, not a cask
219
+ logger.step('Checking Java...');
220
+
221
+ // Check if java is already available
222
+ if (commandExists('java')) {
223
+ logger.success('Java already installed');
224
+
225
+ // Still configure JAVA_HOME if not set
226
+ if (!process.env.JAVA_HOME) {
227
+ await configureJavaHome();
228
+ }
229
+
230
+ return { success: true, skipped: true };
231
+ }
232
+
233
+ logger.step('Installing OpenJDK...');
234
+ const result = execCommand('brew install openjdk');
235
+
236
+ if (result.success) {
237
+ logger.success('OpenJDK installed successfully');
238
+
239
+ // Configure Java
240
+ await configureJavaHome();
241
+
242
+ return { success: true, skipped: false };
243
+ } else {
244
+ logger.error('Failed to install OpenJDK');
245
+ return { success: false, skipped: false };
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Configure JAVA_HOME for macOS
251
+ */
252
+ async function configureJavaHome() {
253
+ logger.step('Configuring Java...');
254
+
255
+ // Create symlink for system Java wrappers
256
+ execCommand(
257
+ 'sudo ln -sfn $(brew --prefix)/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk 2>/dev/null || true',
258
+ { silent: true, showCommand: false }
259
+ );
260
+
261
+ // Set JAVA_HOME in shell config
262
+ const shellConfig = process.env.SHELL?.includes('zsh')
263
+ ? '~/.zshrc'
264
+ : '~/.bash_profile';
265
+
266
+ // Check if already configured
267
+ const alreadySet = execSilent(`grep -q 'JAVA_HOME' ${shellConfig} 2>/dev/null`);
268
+
269
+ if (!alreadySet.success) {
270
+ execCommand(
271
+ `echo 'export JAVA_HOME="$(/usr/libexec/java_home)"' >> ${shellConfig}`,
272
+ { silent: true, showCommand: false }
273
+ );
274
+ execCommand(
275
+ `echo 'export PATH="$JAVA_HOME/bin:$PATH"' >> ${shellConfig}`,
276
+ { silent: true, showCommand: false }
277
+ );
278
+ logger.success('JAVA_HOME configured');
279
+ } else {
280
+ logger.info('JAVA_HOME already configured in shell');
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Run base system setup for macOS
286
+ */
287
+ async function setupBase() {
288
+ const results = {
289
+ installed: [],
290
+ skipped: [],
291
+ failed: []
292
+ };
293
+
294
+ // Install Xcode CLI Tools
295
+ const xcodeResult = await installXcodeCliTools();
296
+ if (xcodeResult.pending) {
297
+ return { ...results, pending: true };
298
+ }
299
+ if (xcodeResult.success) {
300
+ (xcodeResult.skipped ? results.skipped : results.installed).push(
301
+ 'Xcode CLI Tools'
302
+ );
303
+ } else {
304
+ results.failed.push('Xcode CLI Tools');
305
+ }
306
+
307
+ // Install Homebrew
308
+ const brewResult = await installHomebrew();
309
+ if (brewResult.success) {
310
+ (brewResult.skipped ? results.skipped : results.installed).push('Homebrew');
311
+ } else {
312
+ results.failed.push('Homebrew');
313
+ return results; // Can't continue without Homebrew
314
+ }
315
+
316
+ // Install Git
317
+ const gitResult = await installGit();
318
+ if (gitResult.success) {
319
+ (gitResult.skipped ? results.skipped : results.installed).push('Git');
320
+ } else {
321
+ results.failed.push('Git');
322
+ }
323
+
324
+ // Install curl
325
+ const curlResult = await installCurl();
326
+ if (curlResult.success) {
327
+ (curlResult.skipped ? results.skipped : results.installed).push('curl');
328
+ } else {
329
+ results.failed.push('curl');
330
+ }
331
+
332
+ return results;
333
+ }
334
+
335
+ module.exports = {
336
+ installXcodeCliTools,
337
+ installHomebrew,
338
+ brewInstall,
339
+ installGit,
340
+ installCurl,
341
+ installNode,
342
+ installVSCode,
343
+ enableVSCodeCli,
344
+ installPython,
345
+ installJava,
346
+ configureJavaHome,
347
+ setupBase
348
+ };
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Windows Platform Installer
3
+ * Handles Windows-specific installations
4
+ */
5
+
6
+ const { execCommand, execSilent, commandExists } = require('../utils/executor');
7
+ const logger = require('../utils/logger');
8
+ const { isElevated } = require('./detector');
9
+ const ora = require('ora');
10
+
11
+ /**
12
+ * Check if running with Administrator privileges
13
+ * @returns {boolean}
14
+ */
15
+ function checkAdminPrivileges() {
16
+ return isElevated();
17
+ }
18
+
19
+ /**
20
+ * Get the available package manager (winget or choco)
21
+ * @returns {string|null}
22
+ */
23
+ function getPackageManager() {
24
+ if (commandExists('winget')) {
25
+ return 'winget';
26
+ }
27
+ if (commandExists('choco')) {
28
+ return 'choco';
29
+ }
30
+ return null;
31
+ }
32
+
33
+ /**
34
+ * Get install command for package manager
35
+ * @param {string} packageManager - Package manager
36
+ * @param {string} packageId - Package ID
37
+ * @returns {string}
38
+ */
39
+ function getInstallCommand(packageManager, packageId) {
40
+ if (packageManager === 'winget') {
41
+ return `winget install --accept-source-agreements --accept-package-agreements -e --id ${packageId}`;
42
+ }
43
+ if (packageManager === 'choco') {
44
+ return `choco install -y ${packageId}`;
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Package name mapping between winget and chocolatey
51
+ */
52
+ const packageMap = {
53
+ git: {
54
+ winget: 'Git.Git',
55
+ choco: 'git'
56
+ },
57
+ nodejs: {
58
+ winget: 'OpenJS.NodeJS.LTS',
59
+ choco: 'nodejs-lts'
60
+ },
61
+ vscode: {
62
+ winget: 'Microsoft.VisualStudioCode',
63
+ choco: 'vscode'
64
+ },
65
+ python: {
66
+ winget: 'Python.Python.3.12',
67
+ choco: 'python'
68
+ },
69
+ java: {
70
+ winget: 'Microsoft.OpenJDK.17',
71
+ choco: 'openjdk17'
72
+ },
73
+ curl: {
74
+ winget: 'cURL.cURL',
75
+ choco: 'curl'
76
+ }
77
+ };
78
+
79
+ /**
80
+ * Get package ID for the package manager
81
+ * @param {string} packageManager - Package manager
82
+ * @param {string} genericName - Generic package name
83
+ * @returns {string}
84
+ */
85
+ function getPackageId(packageManager, genericName) {
86
+ return packageMap[genericName]?.[packageManager] || genericName;
87
+ }
88
+
89
+ /**
90
+ * Install a package using the available package manager
91
+ * @param {string} genericName - Generic package name
92
+ * @param {string} displayName - Display name for logging
93
+ * @param {string} checkCommand - Command to check if installed
94
+ * @returns {Object} Installation result
95
+ */
96
+ async function installPackage(genericName, displayName, checkCommand = null) {
97
+ const name = displayName || genericName;
98
+ const packageManager = getPackageManager();
99
+
100
+ if (!packageManager) {
101
+ logger.error('No package manager available (winget or chocolatey required)');
102
+ return { success: false, skipped: false };
103
+ }
104
+
105
+ logger.step(`Checking ${name}...`);
106
+
107
+ // Check if already installed
108
+ const cmdToCheck = checkCommand || genericName;
109
+ if (commandExists(cmdToCheck)) {
110
+ logger.success(`${name} already installed`);
111
+ return { success: true, skipped: true };
112
+ }
113
+
114
+ const packageId = getPackageId(packageManager, genericName);
115
+ const command = getInstallCommand(packageManager, packageId);
116
+
117
+ logger.step(`Installing ${name} using ${packageManager}...`);
118
+ const result = execCommand(command);
119
+
120
+ if (result.success) {
121
+ logger.success(`${name} installed successfully`);
122
+ return { success: true, skipped: false };
123
+ } else {
124
+ logger.error(`Failed to install ${name}`);
125
+ return { success: false, skipped: false };
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Install Chocolatey package manager
131
+ * @returns {Object} Installation result
132
+ */
133
+ async function installChocolatey() {
134
+ logger.step('Checking Chocolatey...');
135
+
136
+ if (commandExists('choco')) {
137
+ logger.success('Chocolatey already installed');
138
+ return { success: true, skipped: true };
139
+ }
140
+
141
+ if (!checkAdminPrivileges()) {
142
+ logger.error('Administrator privileges required to install Chocolatey');
143
+ logger.info('Please run this tool as Administrator');
144
+ return { success: false, skipped: false, needsAdmin: true };
145
+ }
146
+
147
+ logger.step('Installing Chocolatey...');
148
+
149
+ const installScript = `
150
+ Set-ExecutionPolicy Bypass -Scope Process -Force;
151
+ [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;
152
+ iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
153
+ `;
154
+
155
+ const result = execCommand(`powershell -Command "${installScript.replace(/\n/g, ' ')}"`);
156
+
157
+ if (result.success) {
158
+ logger.success('Chocolatey installed successfully');
159
+ return { success: true, skipped: false };
160
+ } else {
161
+ logger.error('Failed to install Chocolatey');
162
+ return { success: false, skipped: false };
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Install Git
168
+ */
169
+ async function installGit() {
170
+ return installPackage('git', 'Git', 'git');
171
+ }
172
+
173
+ /**
174
+ * Install curl
175
+ */
176
+ async function installCurl() {
177
+ // curl is included in Windows 10+ by default
178
+ if (commandExists('curl')) {
179
+ logger.success('curl already available (built into Windows)');
180
+ return { success: true, skipped: true };
181
+ }
182
+ return installPackage('curl', 'curl', 'curl');
183
+ }
184
+
185
+ /**
186
+ * Install Node.js
187
+ */
188
+ async function installNode() {
189
+ return installPackage('nodejs', 'Node.js LTS', 'node');
190
+ }
191
+
192
+ /**
193
+ * Install Visual Studio Code
194
+ */
195
+ async function installVSCode() {
196
+ return installPackage('vscode', 'Visual Studio Code', 'code');
197
+ }
198
+
199
+ /**
200
+ * Enable VS Code CLI
201
+ */
202
+ async function enableVSCodeCli() {
203
+ logger.step('Checking VS Code CLI...');
204
+
205
+ if (commandExists('code')) {
206
+ logger.success('VS Code CLI already available');
207
+ return { success: true, skipped: true };
208
+ }
209
+
210
+ // VS Code installer usually adds to PATH, but may need a terminal restart
211
+ logger.warn('VS Code CLI may require terminal restart to be available');
212
+ return { success: true, skipped: true };
213
+ }
214
+
215
+ /**
216
+ * Install Python
217
+ */
218
+ async function installPython() {
219
+ return installPackage('python', 'Python', 'python');
220
+ }
221
+
222
+ /**
223
+ * Install Java (OpenJDK)
224
+ */
225
+ async function installJava() {
226
+ const result = await installPackage('java', 'OpenJDK 17', 'java');
227
+
228
+ if (result.success && !result.skipped) {
229
+ // Set JAVA_HOME
230
+ logger.step('Configuring JAVA_HOME...');
231
+
232
+ const javaHome = execSilent('where java');
233
+ if (javaHome.success && javaHome.output) {
234
+ const javaPath = javaHome.output.split('\n')[0].trim();
235
+ const javaBinDir = javaPath.replace('\\bin\\java.exe', '');
236
+
237
+ // Set user environment variable
238
+ execCommand(
239
+ `setx JAVA_HOME "${javaBinDir}"`,
240
+ { silent: true }
241
+ );
242
+ logger.success('JAVA_HOME configured');
243
+ }
244
+ }
245
+
246
+ return result;
247
+ }
248
+
249
+ /**
250
+ * Check PowerShell availability
251
+ */
252
+ function checkPowerShell() {
253
+ return commandExists('powershell') || commandExists('pwsh');
254
+ }
255
+
256
+ /**
257
+ * Run base system setup for Windows
258
+ */
259
+ async function setupBase() {
260
+ const results = {
261
+ installed: [],
262
+ skipped: [],
263
+ failed: []
264
+ };
265
+
266
+ // Check PowerShell
267
+ if (!checkPowerShell()) {
268
+ logger.error('PowerShell is required but not found');
269
+ results.failed.push('PowerShell');
270
+ return results;
271
+ }
272
+ results.skipped.push('PowerShell');
273
+
274
+ // Check admin privileges
275
+ if (!checkAdminPrivileges()) {
276
+ logger.warn('Not running as Administrator');
277
+ logger.info('Some installations may fail. Consider running as Administrator.');
278
+ }
279
+
280
+ // Check/install package manager
281
+ let packageManager = getPackageManager();
282
+
283
+ if (!packageManager) {
284
+ logger.info('No package manager found. Installing Chocolatey...');
285
+ const chocoResult = await installChocolatey();
286
+ if (chocoResult.success) {
287
+ results.installed.push('Chocolatey');
288
+ packageManager = 'choco';
289
+ } else {
290
+ results.failed.push('Chocolatey');
291
+ logger.error('Cannot continue without a package manager');
292
+ return results;
293
+ }
294
+ } else {
295
+ logger.success(`Using package manager: ${packageManager}`);
296
+ results.skipped.push(packageManager === 'winget' ? 'winget' : 'Chocolatey');
297
+ }
298
+
299
+ // Install Git
300
+ const gitResult = await installGit();
301
+ if (gitResult.success) {
302
+ (gitResult.skipped ? results.skipped : results.installed).push('Git');
303
+ } else {
304
+ results.failed.push('Git');
305
+ }
306
+
307
+ // Install/check curl
308
+ const curlResult = await installCurl();
309
+ if (curlResult.success) {
310
+ (curlResult.skipped ? results.skipped : results.installed).push('curl');
311
+ } else {
312
+ results.failed.push('curl');
313
+ }
314
+
315
+ return results;
316
+ }
317
+
318
+ module.exports = {
319
+ checkAdminPrivileges,
320
+ getPackageManager,
321
+ getInstallCommand,
322
+ getPackageId,
323
+ installPackage,
324
+ installChocolatey,
325
+ installGit,
326
+ installCurl,
327
+ installNode,
328
+ installVSCode,
329
+ enableVSCodeCli,
330
+ installPython,
331
+ installJava,
332
+ checkPowerShell,
333
+ setupBase
334
+ };