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.
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/bin/auto-setup.js +13 -0
- package/package.json +58 -0
- package/src/cli.js +295 -0
- package/src/config/extensions.js +51 -0
- package/src/index.js +16 -0
- package/src/platform/detector.js +240 -0
- package/src/platform/index.js +36 -0
- package/src/platform/linux.js +412 -0
- package/src/platform/macos.js +348 -0
- package/src/platform/windows.js +334 -0
- package/src/stacks/base.js +47 -0
- package/src/stacks/index.js +16 -0
- package/src/stacks/java.js +232 -0
- package/src/stacks/python.js +433 -0
- package/src/stacks/web.js +208 -0
- package/src/utils/executor.js +227 -0
- package/src/utils/index.js +18 -0
- package/src/utils/logger.js +235 -0
- package/src/utils/postinstall.js +295 -0
- package/src/utils/prompts.js +168 -0
- package/src/utils/verifier.js +204 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Stack Installer
|
|
3
|
+
* Installs Python, pip, pipx, virtualenv, Jupyter, NumPy, Pandas
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getPlatformModule } = require('../platform');
|
|
7
|
+
const { execCommand, commandExists, getVersion } = require('../utils/executor');
|
|
8
|
+
const logger = require('../utils/logger');
|
|
9
|
+
const { verifyPythonStack, displayVerification } = require('../utils/verifier');
|
|
10
|
+
const { VSCODE_EXTENSIONS } = require('../config/extensions');
|
|
11
|
+
const { installVSCodeExtensions } = require('./web');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the Python and pip commands for the current platform
|
|
15
|
+
* @returns {Object} Python command names
|
|
16
|
+
*/
|
|
17
|
+
function getPythonCommands() {
|
|
18
|
+
const platform = process.platform;
|
|
19
|
+
|
|
20
|
+
if (platform === 'win32') {
|
|
21
|
+
return {
|
|
22
|
+
python: 'python',
|
|
23
|
+
pip: 'pip'
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// On Unix, prefer python3/pip3
|
|
28
|
+
return {
|
|
29
|
+
python: commandExists('python3') ? 'python3' : 'python',
|
|
30
|
+
pip: commandExists('pip3') ? 'pip3' : 'pip'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a Python package is installed
|
|
36
|
+
* @param {string} packageName - Package name to check
|
|
37
|
+
* @returns {boolean} True if installed
|
|
38
|
+
*/
|
|
39
|
+
function isPythonPackageInstalled(packageName) {
|
|
40
|
+
const { python } = getPythonCommands();
|
|
41
|
+
const result = execCommand(
|
|
42
|
+
`${python} -c "import ${packageName}" 2>&1`,
|
|
43
|
+
{ silent: true, showCommand: false }
|
|
44
|
+
);
|
|
45
|
+
return result.success;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if a pip package is installed (for CLI tools)
|
|
50
|
+
* @param {string} packageName - Package name to check
|
|
51
|
+
* @returns {boolean} True if installed
|
|
52
|
+
*/
|
|
53
|
+
function isPipPackageInstalled(packageName) {
|
|
54
|
+
const { pip } = getPythonCommands();
|
|
55
|
+
const result = execCommand(
|
|
56
|
+
`${pip} show ${packageName}`,
|
|
57
|
+
{ silent: true, showCommand: false }
|
|
58
|
+
);
|
|
59
|
+
return result.success;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Install pipx
|
|
64
|
+
* @param {string} pipCmd - pip command to use
|
|
65
|
+
* @returns {Object} Installation result
|
|
66
|
+
*/
|
|
67
|
+
async function installPipx(pipCmd) {
|
|
68
|
+
logger.step('Checking pipx...');
|
|
69
|
+
|
|
70
|
+
// Check if pipx is installed as a package
|
|
71
|
+
if (isPipPackageInstalled('pipx') || commandExists('pipx')) {
|
|
72
|
+
logger.success('pipx already installed');
|
|
73
|
+
return { success: true, skipped: true };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
logger.step('Installing pipx...');
|
|
77
|
+
|
|
78
|
+
const result = execCommand(`${pipCmd} install --user pipx`);
|
|
79
|
+
|
|
80
|
+
if (result.success) {
|
|
81
|
+
// Ensure pipx path is set
|
|
82
|
+
execCommand('python3 -m pipx ensurepath 2>/dev/null || python -m pipx ensurepath 2>/dev/null', {
|
|
83
|
+
silent: true,
|
|
84
|
+
showCommand: false
|
|
85
|
+
});
|
|
86
|
+
logger.success('pipx installed');
|
|
87
|
+
return { success: true, skipped: false };
|
|
88
|
+
} else {
|
|
89
|
+
logger.error('Failed to install pipx');
|
|
90
|
+
return { success: false, skipped: false };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Install virtualenv
|
|
96
|
+
* @param {string} pipCmd - pip command to use
|
|
97
|
+
* @returns {Object} Installation result
|
|
98
|
+
*/
|
|
99
|
+
async function installVirtualenv(pipCmd) {
|
|
100
|
+
logger.step('Checking virtualenv...');
|
|
101
|
+
|
|
102
|
+
// Check if virtualenv is installed as a package
|
|
103
|
+
if (isPipPackageInstalled('virtualenv') || commandExists('virtualenv')) {
|
|
104
|
+
logger.success('virtualenv already installed');
|
|
105
|
+
return { success: true, skipped: true };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
logger.step('Installing virtualenv...');
|
|
109
|
+
|
|
110
|
+
const result = execCommand(`${pipCmd} install --user virtualenv`);
|
|
111
|
+
|
|
112
|
+
if (result.success) {
|
|
113
|
+
logger.success('virtualenv installed');
|
|
114
|
+
return { success: true, skipped: false };
|
|
115
|
+
} else {
|
|
116
|
+
logger.error('Failed to install virtualenv');
|
|
117
|
+
return { success: false, skipped: false };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Install Jupyter Notebook
|
|
123
|
+
* @param {string} pipCmd - pip command to use
|
|
124
|
+
* @returns {Object} Installation result
|
|
125
|
+
*/
|
|
126
|
+
async function installJupyter(pipCmd) {
|
|
127
|
+
logger.step('Checking Jupyter Notebook...');
|
|
128
|
+
|
|
129
|
+
// Check if jupyter is installed as a package
|
|
130
|
+
if (isPipPackageInstalled('jupyter') || commandExists('jupyter')) {
|
|
131
|
+
logger.success('Jupyter already installed');
|
|
132
|
+
return { success: true, skipped: true };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
logger.step('Installing Jupyter Notebook...');
|
|
136
|
+
|
|
137
|
+
// Use pipx if available, otherwise pip
|
|
138
|
+
let result;
|
|
139
|
+
if (commandExists('pipx')) {
|
|
140
|
+
result = execCommand('pipx install jupyter');
|
|
141
|
+
} else {
|
|
142
|
+
result = execCommand(`${pipCmd} install --user jupyter`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (result.success) {
|
|
146
|
+
logger.success('Jupyter Notebook installed');
|
|
147
|
+
return { success: true, skipped: false };
|
|
148
|
+
} else {
|
|
149
|
+
logger.error('Failed to install Jupyter Notebook');
|
|
150
|
+
return { success: false, skipped: false };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Install NumPy and Pandas
|
|
156
|
+
* @param {string} pipCmd - pip command to use
|
|
157
|
+
* @returns {Object} Installation result
|
|
158
|
+
*/
|
|
159
|
+
async function installDataScienceLibs(pipCmd) {
|
|
160
|
+
logger.step('Checking NumPy and Pandas...');
|
|
161
|
+
|
|
162
|
+
// Check if already installed
|
|
163
|
+
const numpyInstalled = isPythonPackageInstalled('numpy');
|
|
164
|
+
const pandasInstalled = isPythonPackageInstalled('pandas');
|
|
165
|
+
|
|
166
|
+
if (numpyInstalled && pandasInstalled) {
|
|
167
|
+
logger.success('NumPy and Pandas already installed');
|
|
168
|
+
return { success: true, skipped: true };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
logger.step('Installing NumPy and Pandas...');
|
|
172
|
+
|
|
173
|
+
let numpyResult = { success: true };
|
|
174
|
+
let pandasResult = { success: true };
|
|
175
|
+
|
|
176
|
+
if (!numpyInstalled) {
|
|
177
|
+
numpyResult = execCommand(`${pipCmd} install --user numpy`, { silent: true });
|
|
178
|
+
}
|
|
179
|
+
if (!pandasInstalled) {
|
|
180
|
+
pandasResult = execCommand(`${pipCmd} install --user pandas`, { silent: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (numpyResult.success && pandasResult.success) {
|
|
184
|
+
logger.success('NumPy and Pandas installed');
|
|
185
|
+
return { success: true, skipped: false };
|
|
186
|
+
} else {
|
|
187
|
+
logger.warn('Some data science libraries may have failed to install');
|
|
188
|
+
return { success: false, skipped: false };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Configure Python PATH and aliases
|
|
194
|
+
*/
|
|
195
|
+
async function configurePythonPath() {
|
|
196
|
+
const platform = process.platform;
|
|
197
|
+
|
|
198
|
+
if (platform === 'win32') {
|
|
199
|
+
// Windows: Add Python Scripts to PATH
|
|
200
|
+
logger.step('Configuring Python PATH for Windows...');
|
|
201
|
+
const { python } = getPythonCommands();
|
|
202
|
+
|
|
203
|
+
// Get the correct user scripts directory using sysconfig
|
|
204
|
+
const scriptsResult = execCommand(
|
|
205
|
+
`${python} -c "import sysconfig; print(sysconfig.get_path('scripts', 'nt_user'))"`,
|
|
206
|
+
{ silent: true, showCommand: false }
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (scriptsResult.success && scriptsResult.output) {
|
|
210
|
+
const scriptsDir = scriptsResult.output.trim();
|
|
211
|
+
|
|
212
|
+
logger.info(`Python user scripts directory: ${scriptsDir}`);
|
|
213
|
+
|
|
214
|
+
// Check if already in PATH
|
|
215
|
+
const currentPath = process.env.PATH || '';
|
|
216
|
+
if (currentPath.toLowerCase().includes(scriptsDir.toLowerCase())) {
|
|
217
|
+
logger.info('Python Scripts directory already in PATH');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Add to user PATH permanently using PowerShell
|
|
222
|
+
logger.step('Adding Python Scripts to user PATH...');
|
|
223
|
+
|
|
224
|
+
// Escape backslashes for PowerShell
|
|
225
|
+
const escapedScriptsDir = scriptsDir.replace(/\\/g, '\\\\');
|
|
226
|
+
|
|
227
|
+
const psCommand = `$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User'); if ($currentPath -notlike '*${escapedScriptsDir}*') { $newPath = $currentPath + ';${scriptsDir}'; [Environment]::SetEnvironmentVariable('Path', $newPath, 'User'); Write-Host 'PATH updated successfully' } else { Write-Host 'Already in PATH' }`;
|
|
228
|
+
|
|
229
|
+
const result = execCommand(`powershell -Command "${psCommand}"`, {
|
|
230
|
+
silent: true,
|
|
231
|
+
showCommand: false
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (result.success) {
|
|
235
|
+
logger.success('Python Scripts added to PATH');
|
|
236
|
+
logger.info('Please restart your terminal for changes to take effect.');
|
|
237
|
+
} else {
|
|
238
|
+
logger.warn('Could not automatically add to PATH.');
|
|
239
|
+
logger.info(`Manually add this to your PATH: ${scriptsDir}`);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
// Fallback: try to detect from pip show
|
|
243
|
+
logger.warn('Could not detect Python scripts directory');
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
logger.step('Configuring Python PATH...');
|
|
249
|
+
|
|
250
|
+
const shellConfig = process.env.SHELL?.includes('zsh')
|
|
251
|
+
? `${process.env.HOME}/.zshrc`
|
|
252
|
+
: `${process.env.HOME}/.bashrc`;
|
|
253
|
+
|
|
254
|
+
// Add user site-packages to PATH
|
|
255
|
+
const { python } = getPythonCommands();
|
|
256
|
+
const userBase = execCommand(`${python} -m site --user-base`, {
|
|
257
|
+
silent: true,
|
|
258
|
+
showCommand: false
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (userBase.success && userBase.output) {
|
|
262
|
+
const userBin = `${userBase.output.trim()}/bin`;
|
|
263
|
+
|
|
264
|
+
// Check if already in PATH config
|
|
265
|
+
const checkExisting = execCommand(`grep -q '${userBin}' "${shellConfig}" 2>/dev/null`, {
|
|
266
|
+
silent: true,
|
|
267
|
+
showCommand: false
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (!checkExisting.success) {
|
|
271
|
+
// Add to shell config
|
|
272
|
+
const pathExport = `\n# Python user bin (added by auto-dev-setup)\nexport PATH="${userBin}:$PATH"\n`;
|
|
273
|
+
|
|
274
|
+
const fs = require('fs');
|
|
275
|
+
try {
|
|
276
|
+
fs.appendFileSync(shellConfig.replace('~', process.env.HOME), pathExport);
|
|
277
|
+
logger.success(`Added ${userBin} to PATH in ${shellConfig}`);
|
|
278
|
+
} catch (err) {
|
|
279
|
+
logger.warn(`Could not update ${shellConfig}: ${err.message}`);
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
logger.info('Python user bin already in PATH');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Also export for current session
|
|
286
|
+
process.env.PATH = `${userBin}:${process.env.PATH}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Add safe Python aliases (only if not already set)
|
|
290
|
+
const checkAliases = execCommand(`grep -q "alias py='python3'" "${shellConfig}" 2>/dev/null`, {
|
|
291
|
+
silent: true,
|
|
292
|
+
showCommand: false
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
if (!checkAliases.success) {
|
|
296
|
+
const aliases = `
|
|
297
|
+
# Python aliases (added by auto-dev-setup)
|
|
298
|
+
alias py='python3'
|
|
299
|
+
alias pip='pip3'
|
|
300
|
+
`;
|
|
301
|
+
const fs = require('fs');
|
|
302
|
+
try {
|
|
303
|
+
fs.appendFileSync(shellConfig.replace('~', process.env.HOME), aliases);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
// Ignore alias errors
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
logger.success('Python PATH configured');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Install the Python development stack
|
|
314
|
+
* @returns {Object} Installation results
|
|
315
|
+
*/
|
|
316
|
+
async function install() {
|
|
317
|
+
logger.section('Step 4: Python Development Environment');
|
|
318
|
+
|
|
319
|
+
const platformModule = getPlatformModule();
|
|
320
|
+
const results = {
|
|
321
|
+
installed: [],
|
|
322
|
+
skipped: [],
|
|
323
|
+
failed: []
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Install Python
|
|
327
|
+
const pythonResult = await platformModule.installPython();
|
|
328
|
+
if (pythonResult.success) {
|
|
329
|
+
(pythonResult.skipped ? results.skipped : results.installed).push('Python');
|
|
330
|
+
} else {
|
|
331
|
+
results.failed.push('Python');
|
|
332
|
+
return results; // Can't continue without Python
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Get appropriate commands
|
|
336
|
+
const { python, pip } = getPythonCommands();
|
|
337
|
+
|
|
338
|
+
// Install pipx
|
|
339
|
+
const pipxResult = await installPipx(pip);
|
|
340
|
+
if (pipxResult.success) {
|
|
341
|
+
(pipxResult.skipped ? results.skipped : results.installed).push('pipx');
|
|
342
|
+
} else {
|
|
343
|
+
results.failed.push('pipx');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Install virtualenv
|
|
347
|
+
const venvResult = await installVirtualenv(pip);
|
|
348
|
+
if (venvResult.success) {
|
|
349
|
+
(venvResult.skipped ? results.skipped : results.installed).push('virtualenv');
|
|
350
|
+
} else {
|
|
351
|
+
results.failed.push('virtualenv');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Install Jupyter
|
|
355
|
+
const jupyterResult = await installJupyter(pip);
|
|
356
|
+
if (jupyterResult.success) {
|
|
357
|
+
(jupyterResult.skipped ? results.skipped : results.installed).push(
|
|
358
|
+
'Jupyter Notebook'
|
|
359
|
+
);
|
|
360
|
+
} else {
|
|
361
|
+
results.failed.push('Jupyter Notebook');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Install NumPy and Pandas
|
|
365
|
+
const dataResult = await installDataScienceLibs(pip);
|
|
366
|
+
if (dataResult.success) {
|
|
367
|
+
if (dataResult.skipped) {
|
|
368
|
+
results.skipped.push('NumPy', 'Pandas');
|
|
369
|
+
} else {
|
|
370
|
+
results.installed.push('NumPy', 'Pandas');
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
results.failed.push('NumPy/Pandas');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Configure PATH and aliases
|
|
377
|
+
await configurePythonPath();
|
|
378
|
+
|
|
379
|
+
// Install Python VS Code extensions
|
|
380
|
+
if (commandExists('code')) {
|
|
381
|
+
const extResults = await installVSCodeExtensions(VSCODE_EXTENSIONS.python);
|
|
382
|
+
results.installed.push(...extResults.installed.map((e) => `VS Code: ${e}`));
|
|
383
|
+
results.skipped.push(...extResults.skipped.map((e) => `VS Code: ${e}`));
|
|
384
|
+
results.failed.push(...extResults.failed.map((e) => `VS Code: ${e}`));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Verify installations
|
|
388
|
+
logger.newline();
|
|
389
|
+
logger.subsection('Python Stack Verification:');
|
|
390
|
+
|
|
391
|
+
const verifyPython = execCommand(`${python} --version`, {
|
|
392
|
+
silent: true,
|
|
393
|
+
showCommand: true
|
|
394
|
+
});
|
|
395
|
+
const verifyPip = execCommand(`${pip} --version`, {
|
|
396
|
+
silent: true,
|
|
397
|
+
showCommand: true
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
if (verifyPython.success) {
|
|
401
|
+
logger.success(`Python: ${verifyPython.output}`);
|
|
402
|
+
} else {
|
|
403
|
+
logger.error('Python: not found');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (verifyPip.success) {
|
|
407
|
+
logger.success(`pip: ${verifyPip.output.split(' ')[0]} ${verifyPip.output.split(' ')[1]}`);
|
|
408
|
+
} else {
|
|
409
|
+
logger.error('pip: not found');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return results;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Check if Python stack is already installed
|
|
417
|
+
* @returns {boolean}
|
|
418
|
+
*/
|
|
419
|
+
function isInstalled() {
|
|
420
|
+
const { python, pip } = getPythonCommands();
|
|
421
|
+
return commandExists(python) && commandExists(pip);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
module.exports = {
|
|
425
|
+
install,
|
|
426
|
+
isInstalled,
|
|
427
|
+
getPythonCommands,
|
|
428
|
+
installPipx,
|
|
429
|
+
installVirtualenv,
|
|
430
|
+
installJupyter,
|
|
431
|
+
installDataScienceLibs,
|
|
432
|
+
configurePythonPath
|
|
433
|
+
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Developer Stack Installer
|
|
3
|
+
* Installs Node.js, npm, VS Code, and essential extensions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getPlatformModule } = require('../platform');
|
|
7
|
+
const { execCommand, commandExists } = require('../utils/executor');
|
|
8
|
+
const logger = require('../utils/logger');
|
|
9
|
+
const { verifyWebStack, displayVerification } = require('../utils/verifier');
|
|
10
|
+
const { VSCODE_EXTENSIONS } = require('../config/extensions');
|
|
11
|
+
const ora = require('ora');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Install VS Code extensions
|
|
15
|
+
* @param {Array} extensions - Array of extension objects
|
|
16
|
+
* @returns {Object} Installation results
|
|
17
|
+
*/
|
|
18
|
+
async function installVSCodeExtensions(extensions) {
|
|
19
|
+
const results = {
|
|
20
|
+
installed: [],
|
|
21
|
+
skipped: [],
|
|
22
|
+
failed: []
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (!commandExists('code')) {
|
|
26
|
+
logger.warn('VS Code CLI not available. Cannot install extensions.');
|
|
27
|
+
return results;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
logger.step('Installing VS Code extensions...');
|
|
31
|
+
|
|
32
|
+
// Get list of installed extensions (cross-platform)
|
|
33
|
+
const listResult = execCommand('code --list-extensions', {
|
|
34
|
+
silent: true,
|
|
35
|
+
showCommand: false
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const installedExtensions = listResult.success
|
|
39
|
+
? listResult.output.toLowerCase().split('\n').map((e) => e.trim())
|
|
40
|
+
: [];
|
|
41
|
+
|
|
42
|
+
for (const ext of extensions) {
|
|
43
|
+
// Check if already installed (cross-platform check without grep)
|
|
44
|
+
const isInstalled = installedExtensions.includes(ext.id.toLowerCase());
|
|
45
|
+
|
|
46
|
+
if (isInstalled) {
|
|
47
|
+
logger.listItem(`${ext.name} - already installed`);
|
|
48
|
+
results.skipped.push(ext.name);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
logger.listItem(`Installing ${ext.name}...`);
|
|
53
|
+
const installResult = execCommand(
|
|
54
|
+
`code --install-extension ${ext.id} --force`,
|
|
55
|
+
{ silent: true, showCommand: false }
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (installResult.success) {
|
|
59
|
+
results.installed.push(ext.name);
|
|
60
|
+
} else {
|
|
61
|
+
results.failed.push(ext.name);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Verify npm global install works
|
|
70
|
+
* @returns {boolean}
|
|
71
|
+
*/
|
|
72
|
+
async function verifyNpmGlobal() {
|
|
73
|
+
logger.step('Verifying npm global install configuration...');
|
|
74
|
+
|
|
75
|
+
// Check npm prefix
|
|
76
|
+
const prefixResult = execCommand('npm config get prefix', {
|
|
77
|
+
silent: true,
|
|
78
|
+
showCommand: false
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (prefixResult.success) {
|
|
82
|
+
logger.info(`npm global prefix: ${prefixResult.output}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Try to ensure npm global installs work without sudo
|
|
86
|
+
const platform = process.platform;
|
|
87
|
+
if (platform !== 'win32') {
|
|
88
|
+
const npmDir = prefixResult.output || '~/.npm-global';
|
|
89
|
+
|
|
90
|
+
// Check if we need to configure npm prefix
|
|
91
|
+
if (
|
|
92
|
+
prefixResult.output?.includes('/usr/local') ||
|
|
93
|
+
prefixResult.output?.includes('/usr/lib')
|
|
94
|
+
) {
|
|
95
|
+
logger.info('Configuring npm for user-space global installs...');
|
|
96
|
+
|
|
97
|
+
execCommand(`mkdir -p ~/.npm-global`, { silent: true, showCommand: false });
|
|
98
|
+
execCommand(`npm config set prefix '~/.npm-global'`, {
|
|
99
|
+
silent: true,
|
|
100
|
+
showCommand: false
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Add to PATH
|
|
104
|
+
const shellConfig = process.env.SHELL?.includes('zsh')
|
|
105
|
+
? '~/.zshrc'
|
|
106
|
+
: '~/.bashrc';
|
|
107
|
+
|
|
108
|
+
execCommand(
|
|
109
|
+
`grep -q 'npm-global' ${shellConfig} || echo 'export PATH=~/.npm-global/bin:$PATH' >> ${shellConfig}`,
|
|
110
|
+
{ silent: true, showCommand: false }
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
logger.success('npm global configuration updated');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Install the web developer stack
|
|
122
|
+
* @returns {Object} Installation results
|
|
123
|
+
*/
|
|
124
|
+
async function install() {
|
|
125
|
+
logger.section('Step 3: Web Developer Stack (Mandatory)');
|
|
126
|
+
|
|
127
|
+
const platform = getPlatformModule();
|
|
128
|
+
const results = {
|
|
129
|
+
installed: [],
|
|
130
|
+
skipped: [],
|
|
131
|
+
failed: []
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Install Node.js
|
|
135
|
+
const nodeResult = await platform.installNode();
|
|
136
|
+
if (nodeResult.success) {
|
|
137
|
+
(nodeResult.skipped ? results.skipped : results.installed).push('Node.js');
|
|
138
|
+
} else {
|
|
139
|
+
results.failed.push('Node.js');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Verify npm
|
|
143
|
+
await verifyNpmGlobal();
|
|
144
|
+
|
|
145
|
+
// Install VS Code
|
|
146
|
+
const vscodeResult = await platform.installVSCode();
|
|
147
|
+
if (vscodeResult.success) {
|
|
148
|
+
(vscodeResult.skipped ? results.skipped : results.installed).push(
|
|
149
|
+
'Visual Studio Code'
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
results.failed.push('Visual Studio Code');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Enable VS Code CLI
|
|
156
|
+
await platform.enableVSCodeCli();
|
|
157
|
+
|
|
158
|
+
// Install essential VS Code extensions
|
|
159
|
+
const extResults = await installVSCodeExtensions(VSCODE_EXTENSIONS.essential);
|
|
160
|
+
results.installed.push(...extResults.installed.map((e) => `VS Code: ${e}`));
|
|
161
|
+
results.skipped.push(...extResults.skipped.map((e) => `VS Code: ${e}`));
|
|
162
|
+
results.failed.push(...extResults.failed.map((e) => `VS Code: ${e}`));
|
|
163
|
+
|
|
164
|
+
// Verify installations
|
|
165
|
+
logger.newline();
|
|
166
|
+
logger.subsection('Web Stack Verification:');
|
|
167
|
+
|
|
168
|
+
const verifyNode = execCommand('node -v', { silent: true, showCommand: true });
|
|
169
|
+
const verifyNpm = execCommand('npm -v', { silent: true, showCommand: true });
|
|
170
|
+
const verifyGit = execCommand('git --version', { silent: true, showCommand: true });
|
|
171
|
+
|
|
172
|
+
if (verifyNode.success) {
|
|
173
|
+
logger.success(`Node.js: ${verifyNode.output}`);
|
|
174
|
+
} else {
|
|
175
|
+
logger.error('Node.js: not found');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (verifyNpm.success) {
|
|
179
|
+
logger.success(`npm: ${verifyNpm.output}`);
|
|
180
|
+
} else {
|
|
181
|
+
logger.error('npm: not found');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (verifyGit.success) {
|
|
185
|
+
logger.success(`Git: ${verifyGit.output}`);
|
|
186
|
+
} else {
|
|
187
|
+
logger.error('Git: not found');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return results;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Check if web stack is already installed
|
|
195
|
+
* @returns {boolean}
|
|
196
|
+
*/
|
|
197
|
+
function isInstalled() {
|
|
198
|
+
return (
|
|
199
|
+
commandExists('node') && commandExists('npm') && commandExists('git')
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
install,
|
|
205
|
+
isInstalled,
|
|
206
|
+
installVSCodeExtensions,
|
|
207
|
+
verifyNpmGlobal
|
|
208
|
+
};
|