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,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
+ };