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,295 @@
1
+ /**
2
+ * Post-Installation Setup
3
+ * Ensures all installed tools are properly configured and accessible
4
+ */
5
+
6
+ const { execCommand, execSilent, commandExists } = require('./executor');
7
+ const logger = require('./logger');
8
+
9
+ /**
10
+ * Get all paths that should be in PATH for the tools to work
11
+ * @returns {Array} Array of paths to add
12
+ */
13
+ function getRequiredPaths() {
14
+ const platform = process.platform;
15
+ const paths = [];
16
+
17
+ if (platform === 'win32') {
18
+ // Only check Python paths if Python is installed
19
+ if (commandExists('python') || commandExists('python3')) {
20
+ // Python user scripts - use sysconfig for correct versioned path
21
+ const pythonScripts = execSilent(
22
+ 'python -c "import sysconfig; print(sysconfig.get_path(\'scripts\', \'nt_user\'))"'
23
+ );
24
+ if (pythonScripts.success && pythonScripts.output) {
25
+ const scriptsPath = pythonScripts.output.trim();
26
+ if (scriptsPath && scriptsPath !== 'None') {
27
+ paths.push({
28
+ path: scriptsPath,
29
+ name: 'Python Scripts',
30
+ tools: ['jupyter', 'pipx', 'virtualenv']
31
+ });
32
+ }
33
+ }
34
+ }
35
+
36
+ // npm global on Windows - only if npm is installed
37
+ if (commandExists('npm')) {
38
+ const npmPrefix = execSilent('npm config get prefix');
39
+ if (npmPrefix.success && npmPrefix.output) {
40
+ const prefix = npmPrefix.output.trim();
41
+ // On Windows, npm global binaries are in the prefix folder directly
42
+ paths.push({
43
+ path: prefix,
44
+ name: 'npm global',
45
+ tools: ['npm global packages']
46
+ });
47
+ }
48
+ }
49
+
50
+ // Also check for AppData npm location (common on Windows)
51
+ const appData = process.env.APPDATA;
52
+ if (appData) {
53
+ const npmPath = `${appData}\\npm`;
54
+ paths.push({
55
+ path: npmPath,
56
+ name: 'npm AppData',
57
+ tools: ['npm global packages']
58
+ });
59
+ }
60
+ } else {
61
+ // Only check Python paths if Python is installed
62
+ if (commandExists('python3') || commandExists('python')) {
63
+ // Unix: Python user bin - use sysconfig for correct path
64
+ const pythonUserBin = execSilent(
65
+ 'python3 -c "import sysconfig; print(sysconfig.get_path(\'scripts\', scheme=\'posix_user\'))" 2>/dev/null || python -c "import sysconfig; print(sysconfig.get_path(\'scripts\', scheme=\'posix_user\'))"'
66
+ );
67
+ if (pythonUserBin.success && pythonUserBin.output) {
68
+ const binPath = pythonUserBin.output.trim();
69
+ if (binPath && binPath !== 'None') {
70
+ paths.push({
71
+ path: binPath,
72
+ name: 'Python user bin',
73
+ tools: ['jupyter', 'pipx', 'virtualenv']
74
+ });
75
+ }
76
+ }
77
+
78
+ // Fallback: try site --user-base
79
+ if (!pythonUserBin.success) {
80
+ const pythonUserBase = execSilent('python3 -m site --user-base 2>/dev/null || python -m site --user-base');
81
+ if (pythonUserBase.success && pythonUserBase.output) {
82
+ paths.push({
83
+ path: `${pythonUserBase.output.trim()}/bin`,
84
+ name: 'Python user bin',
85
+ tools: ['jupyter', 'pipx', 'virtualenv']
86
+ });
87
+ }
88
+ }
89
+ }
90
+
91
+ // npm global (if npm is installed and configured to user space)
92
+ if (commandExists('npm')) {
93
+ const npmPrefix = execSilent('npm config get prefix');
94
+ if (npmPrefix.success && npmPrefix.output) {
95
+ const prefix = npmPrefix.output.trim();
96
+ // Only add if it's in user space (not /usr/local or /usr)
97
+ if (prefix.includes(process.env.HOME) || prefix.includes('.npm-global')) {
98
+ paths.push({
99
+ path: `${prefix}/bin`,
100
+ name: 'npm global',
101
+ tools: ['npm global packages']
102
+ });
103
+ }
104
+ }
105
+ }
106
+
107
+ // pipx bin directory - always check this on Unix
108
+ const pipxBin = `${process.env.HOME}/.local/bin`;
109
+ paths.push({
110
+ path: pipxBin,
111
+ name: 'pipx/local bin',
112
+ tools: ['pipx installed tools']
113
+ });
114
+
115
+ // Homebrew on Apple Silicon
116
+ if (platform === 'darwin' && process.arch === 'arm64') {
117
+ paths.push({
118
+ path: '/opt/homebrew/bin',
119
+ name: 'Homebrew',
120
+ tools: ['brew packages']
121
+ });
122
+ }
123
+
124
+ // Homebrew on Intel Mac
125
+ if (platform === 'darwin' && process.arch === 'x64') {
126
+ paths.push({
127
+ path: '/usr/local/bin',
128
+ name: 'Homebrew',
129
+ tools: ['brew packages']
130
+ });
131
+ }
132
+ }
133
+
134
+ return paths;
135
+ }
136
+
137
+ /**
138
+ * Check which required paths are missing from current PATH
139
+ * @returns {Array} Missing paths
140
+ */
141
+ function getMissingPaths() {
142
+ const requiredPaths = getRequiredPaths();
143
+ const currentPath = process.env.PATH || '';
144
+ const missing = [];
145
+
146
+ for (const pathInfo of requiredPaths) {
147
+ if (!currentPath.toLowerCase().includes(pathInfo.path.toLowerCase())) {
148
+ missing.push(pathInfo);
149
+ }
150
+ }
151
+
152
+ return missing;
153
+ }
154
+
155
+ /**
156
+ * Add missing paths to user's shell configuration (Unix) or environment (Windows)
157
+ * @returns {Object} Result with paths added
158
+ */
159
+ async function ensurePathsConfigured() {
160
+ const platform = process.platform;
161
+ const requiredPaths = getRequiredPaths();
162
+ const added = [];
163
+
164
+ if (platform === 'win32') {
165
+ // Windows: Add to user PATH environment variable
166
+ for (const pathInfo of requiredPaths) {
167
+ const checkResult = execSilent(
168
+ `powershell -Command "$p = [Environment]::GetEnvironmentVariable('Path', 'User'); $p -like '*${pathInfo.path.replace(/\\/g, '\\\\')}*'"`
169
+ );
170
+
171
+ if (!checkResult.output?.includes('True')) {
172
+ const addResult = execCommand(
173
+ `powershell -Command "$p = [Environment]::GetEnvironmentVariable('Path', 'User'); [Environment]::SetEnvironmentVariable('Path', \\"$p;${pathInfo.path}\\", 'User')"`,
174
+ { silent: true, showCommand: false }
175
+ );
176
+
177
+ if (addResult.success) {
178
+ added.push(pathInfo);
179
+ }
180
+ }
181
+ }
182
+ } else {
183
+ // Unix: Add to shell config
184
+ const shellConfig = process.env.SHELL?.includes('zsh')
185
+ ? `${process.env.HOME}/.zshrc`
186
+ : `${process.env.HOME}/.bashrc`;
187
+
188
+ const fs = require('fs');
189
+
190
+ for (const pathInfo of requiredPaths) {
191
+ // Check if already in config
192
+ const checkResult = execSilent(`grep -q '${pathInfo.path}' "${shellConfig}" 2>/dev/null`);
193
+
194
+ if (!checkResult.success) {
195
+ try {
196
+ const exportLine = `\n# ${pathInfo.name} (added by auto-dev-setup)\nexport PATH="${pathInfo.path}:$PATH"\n`;
197
+ fs.appendFileSync(shellConfig, exportLine);
198
+ added.push(pathInfo);
199
+ } catch (err) {
200
+ // Ignore errors
201
+ }
202
+ }
203
+ }
204
+ }
205
+
206
+ return { added };
207
+ }
208
+
209
+ /**
210
+ * Verify all installed tools are accessible
211
+ * @returns {Object} Verification results
212
+ */
213
+ function verifyToolsAccessible() {
214
+ const tools = [
215
+ { cmd: 'git', name: 'Git', required: true },
216
+ { cmd: 'node', name: 'Node.js', required: true },
217
+ { cmd: 'npm', name: 'npm', required: true },
218
+ { cmd: 'python', name: 'Python', altCmd: 'python3', required: false },
219
+ { cmd: 'pip', name: 'pip', altCmd: 'pip3', required: false },
220
+ { cmd: 'pipx', name: 'pipx', required: false },
221
+ { cmd: 'virtualenv', name: 'virtualenv', required: false },
222
+ { cmd: 'jupyter', name: 'Jupyter', required: false },
223
+ { cmd: 'java', name: 'Java', required: false },
224
+ { cmd: 'code', name: 'VS Code', required: false }
225
+ ];
226
+
227
+ const results = {
228
+ accessible: [],
229
+ notAccessible: [],
230
+ notInstalled: []
231
+ };
232
+
233
+ for (const tool of tools) {
234
+ if (commandExists(tool.cmd) || (tool.altCmd && commandExists(tool.altCmd))) {
235
+ results.accessible.push(tool.name);
236
+ } else {
237
+ results.notAccessible.push(tool.name);
238
+ }
239
+ }
240
+
241
+ return results;
242
+ }
243
+
244
+ /**
245
+ * Display post-installation instructions
246
+ * @param {Object} options - Display options
247
+ */
248
+ function displayPostInstallInstructions(options = {}) {
249
+ const platform = process.platform;
250
+ const missingPaths = getMissingPaths();
251
+
252
+ if (missingPaths.length === 0 && !options.forceShow) {
253
+ return;
254
+ }
255
+
256
+ logger.newline();
257
+ logger.warn('Action Required: Restart your terminal');
258
+ logger.newline();
259
+
260
+ if (platform === 'win32') {
261
+ logger.info('To use all installed tools, please:');
262
+ logger.listItem('Close this PowerShell/Terminal window completely');
263
+ logger.listItem('Open a new PowerShell/Terminal window');
264
+ logger.newline();
265
+ logger.info('Then verify with:');
266
+ logger.command('jupyter --version');
267
+ logger.command('pipx --version');
268
+ logger.command('java --version');
269
+ } else if (platform === 'darwin') {
270
+ logger.info('To use all installed tools, either:');
271
+ logger.listItem('Restart your terminal, OR');
272
+ logger.listItem('Run: source ~/.zshrc');
273
+ logger.newline();
274
+ logger.info('Then verify with:');
275
+ logger.command('jupyter --version');
276
+ logger.command('pipx --version');
277
+ } else {
278
+ logger.info('To use all installed tools, either:');
279
+ logger.listItem('Restart your terminal, OR');
280
+ const shellConfig = process.env.SHELL?.includes('zsh') ? '~/.zshrc' : '~/.bashrc';
281
+ logger.listItem(`Run: source ${shellConfig}`);
282
+ logger.newline();
283
+ logger.info('Then verify with:');
284
+ logger.command('jupyter --version');
285
+ logger.command('pipx --version');
286
+ }
287
+ }
288
+
289
+ module.exports = {
290
+ getRequiredPaths,
291
+ getMissingPaths,
292
+ ensurePathsConfigured,
293
+ verifyToolsAccessible,
294
+ displayPostInstallInstructions
295
+ };
@@ -0,0 +1,168 @@
1
+ /**
2
+ * User Prompt Utility
3
+ * Handles interactive prompts and user input
4
+ */
5
+
6
+ const inquirer = require('inquirer');
7
+ const logger = require('./logger');
8
+
9
+ /**
10
+ * Ask a yes/no question
11
+ * @param {string} message - Question to ask
12
+ * @param {boolean} defaultValue - Default value
13
+ * @returns {Promise<boolean>} User's answer
14
+ */
15
+ async function confirm(message, defaultValue = true) {
16
+ const { answer } = await inquirer.prompt([
17
+ {
18
+ type: 'confirm',
19
+ name: 'answer',
20
+ message,
21
+ default: defaultValue
22
+ }
23
+ ]);
24
+ return answer;
25
+ }
26
+
27
+ /**
28
+ * Ask a multiple choice question
29
+ * @param {string} message - Question to ask
30
+ * @param {Array} choices - Array of choices
31
+ * @returns {Promise<string>} Selected choice
32
+ */
33
+ async function select(message, choices) {
34
+ const { answer } = await inquirer.prompt([
35
+ {
36
+ type: 'list',
37
+ name: 'answer',
38
+ message,
39
+ choices
40
+ }
41
+ ]);
42
+ return answer;
43
+ }
44
+
45
+ /**
46
+ * Ask for text input
47
+ * @param {string} message - Question to ask
48
+ * @param {string} defaultValue - Default value
49
+ * @returns {Promise<string>} User's input
50
+ */
51
+ async function input(message, defaultValue = '') {
52
+ const { answer } = await inquirer.prompt([
53
+ {
54
+ type: 'input',
55
+ name: 'answer',
56
+ message,
57
+ default: defaultValue
58
+ }
59
+ ]);
60
+ return answer;
61
+ }
62
+
63
+ /**
64
+ * Ask for multiple selections
65
+ * @param {string} message - Question to ask
66
+ * @param {Array} choices - Array of choices
67
+ * @returns {Promise<Array>} Selected choices
68
+ */
69
+ async function multiSelect(message, choices) {
70
+ const { answer } = await inquirer.prompt([
71
+ {
72
+ type: 'checkbox',
73
+ name: 'answer',
74
+ message,
75
+ choices
76
+ }
77
+ ]);
78
+ return answer;
79
+ }
80
+
81
+ /**
82
+ * Ask for password input
83
+ * @param {string} message - Question to ask
84
+ * @returns {Promise<string>} User's password
85
+ */
86
+ async function password(message) {
87
+ const { answer } = await inquirer.prompt([
88
+ {
89
+ type: 'password',
90
+ name: 'answer',
91
+ message,
92
+ mask: '*'
93
+ }
94
+ ]);
95
+ return answer;
96
+ }
97
+
98
+ /**
99
+ * Prompt for Python stack installation
100
+ * @returns {Promise<boolean>}
101
+ */
102
+ async function promptPythonStack() {
103
+ logger.newline();
104
+ return confirm(
105
+ 'Do you want to install the full Python development environment?',
106
+ true
107
+ );
108
+ }
109
+
110
+ /**
111
+ * Prompt for Java stack installation
112
+ * @returns {Promise<boolean>}
113
+ */
114
+ async function promptJavaStack() {
115
+ logger.newline();
116
+ return confirm(
117
+ 'Do you want to install the Java development environment?',
118
+ true
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Prompt to continue after warning
124
+ * @param {string} warning - Warning message
125
+ * @returns {Promise<boolean>}
126
+ */
127
+ async function continueAfterWarning(warning) {
128
+ logger.warn(warning);
129
+ return confirm('Do you want to continue anyway?', false);
130
+ }
131
+
132
+ /**
133
+ * Prompt for admin/sudo action
134
+ * @param {string} action - Action description
135
+ * @returns {Promise<boolean>}
136
+ */
137
+ async function promptForElevation(action) {
138
+ logger.newline();
139
+ logger.warn(`The following action requires administrator privileges:`);
140
+ logger.info(action);
141
+ return confirm('Do you want to proceed?', true);
142
+ }
143
+
144
+ /**
145
+ * Press enter to continue
146
+ */
147
+ async function pressEnterToContinue() {
148
+ await inquirer.prompt([
149
+ {
150
+ type: 'input',
151
+ name: 'continue',
152
+ message: 'Press Enter to continue...'
153
+ }
154
+ ]);
155
+ }
156
+
157
+ module.exports = {
158
+ confirm,
159
+ select,
160
+ input,
161
+ multiSelect,
162
+ password,
163
+ promptPythonStack,
164
+ promptJavaStack,
165
+ continueAfterWarning,
166
+ promptForElevation,
167
+ pressEnterToContinue
168
+ };
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Verifier Utility
3
+ * Verifies installations and checks tool availability
4
+ */
5
+
6
+ const { commandExists, getVersion, execSilent } = require('./executor');
7
+ const logger = require('./logger');
8
+
9
+ /**
10
+ * Verify a tool is installed
11
+ * @param {string} command - Command to check
12
+ * @param {string} name - Display name
13
+ * @param {string} versionFlag - Version flag
14
+ * @returns {Object} Verification result
15
+ */
16
+ function verifyTool(command, name, versionFlag = '--version') {
17
+ const exists = commandExists(command);
18
+ const version = exists ? getVersion(command, versionFlag) : null;
19
+
20
+ return {
21
+ name,
22
+ command,
23
+ installed: exists,
24
+ version
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Verify multiple tools
30
+ * @param {Array} tools - Array of tool objects {command, name, versionFlag}
31
+ * @returns {Array} Verification results
32
+ */
33
+ function verifyTools(tools) {
34
+ return tools.map((tool) =>
35
+ verifyTool(tool.command, tool.name, tool.versionFlag || '--version')
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Display verification results
41
+ * @param {Array} results - Verification results
42
+ */
43
+ function displayVerification(results) {
44
+ logger.subsection('Verification Results:');
45
+
46
+ for (const result of results) {
47
+ if (result.installed) {
48
+ logger.success(`${result.name}: ${result.version || 'installed'}`);
49
+ } else {
50
+ logger.error(`${result.name}: not installed`);
51
+ }
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Verify base tools (git, curl/wget)
57
+ * @returns {Object} Verification results
58
+ */
59
+ function verifyBaseTools() {
60
+ const tools = [
61
+ { command: 'git', name: 'Git' },
62
+ { command: 'curl', name: 'curl' }
63
+ ];
64
+
65
+ return verifyTools(tools);
66
+ }
67
+
68
+ /**
69
+ * Verify web stack (Node, npm, VS Code)
70
+ * @returns {Object} Verification results
71
+ */
72
+ function verifyWebStack() {
73
+ const tools = [
74
+ { command: 'node', name: 'Node.js', versionFlag: '-v' },
75
+ { command: 'npm', name: 'npm', versionFlag: '-v' },
76
+ { command: 'git', name: 'Git' },
77
+ { command: 'code', name: 'VS Code' }
78
+ ];
79
+
80
+ return verifyTools(tools);
81
+ }
82
+
83
+ /**
84
+ * Verify Python stack
85
+ * @returns {Object} Verification results
86
+ */
87
+ function verifyPythonStack() {
88
+ const platform = process.platform;
89
+ const pythonCmd = platform === 'win32' ? 'python' : 'python3';
90
+ const pipCmd = platform === 'win32' ? 'pip' : 'pip3';
91
+
92
+ const tools = [
93
+ { command: pythonCmd, name: 'Python' },
94
+ { command: pipCmd, name: 'pip' },
95
+ { command: 'pipx', name: 'pipx' },
96
+ { command: 'virtualenv', name: 'virtualenv' },
97
+ { command: 'jupyter', name: 'Jupyter' }
98
+ ];
99
+
100
+ return verifyTools(tools);
101
+ }
102
+
103
+ /**
104
+ * Verify Java stack
105
+ * @returns {Object} Verification results
106
+ */
107
+ function verifyJavaStack() {
108
+ const tools = [{ command: 'java', name: 'Java' }];
109
+
110
+ const results = verifyTools(tools);
111
+
112
+ // Check JAVA_HOME
113
+ const javaHome = process.env.JAVA_HOME;
114
+ results.push({
115
+ name: 'JAVA_HOME',
116
+ command: 'JAVA_HOME',
117
+ installed: !!javaHome,
118
+ version: javaHome || 'not set'
119
+ });
120
+
121
+ return results;
122
+ }
123
+
124
+ /**
125
+ * Verify VS Code extensions
126
+ * @param {Array} extensions - Extension IDs to check
127
+ * @returns {Array} Installed extensions
128
+ */
129
+ function verifyVSCodeExtensions(extensions) {
130
+ const result = execSilent('code --list-extensions');
131
+
132
+ if (!result.success) {
133
+ return [];
134
+ }
135
+
136
+ const installedExtensions = result.output.toLowerCase().split('\n');
137
+
138
+ return extensions.map((ext) => ({
139
+ id: ext,
140
+ installed: installedExtensions.includes(ext.toLowerCase())
141
+ }));
142
+ }
143
+
144
+ /**
145
+ * Check if Homebrew is installed (macOS)
146
+ * @returns {boolean}
147
+ */
148
+ function isHomebrewInstalled() {
149
+ return commandExists('brew');
150
+ }
151
+
152
+ /**
153
+ * Check if Xcode CLI tools are installed (macOS)
154
+ * @returns {boolean}
155
+ */
156
+ function isXcodeCliInstalled() {
157
+ const result = execSilent('xcode-select -p');
158
+ return result.success;
159
+ }
160
+
161
+ /**
162
+ * Check if winget is available (Windows)
163
+ * @returns {boolean}
164
+ */
165
+ function isWingetAvailable() {
166
+ return commandExists('winget');
167
+ }
168
+
169
+ /**
170
+ * Check if chocolatey is available (Windows)
171
+ * @returns {boolean}
172
+ */
173
+ function isChocolateyAvailable() {
174
+ return commandExists('choco');
175
+ }
176
+
177
+ /**
178
+ * Run all verifications and return summary
179
+ * @returns {Object} Complete verification summary
180
+ */
181
+ function runAllVerifications() {
182
+ return {
183
+ base: verifyBaseTools(),
184
+ web: verifyWebStack(),
185
+ python: verifyPythonStack(),
186
+ java: verifyJavaStack()
187
+ };
188
+ }
189
+
190
+ module.exports = {
191
+ verifyTool,
192
+ verifyTools,
193
+ displayVerification,
194
+ verifyBaseTools,
195
+ verifyWebStack,
196
+ verifyPythonStack,
197
+ verifyJavaStack,
198
+ verifyVSCodeExtensions,
199
+ isHomebrewInstalled,
200
+ isXcodeCliInstalled,
201
+ isWingetAvailable,
202
+ isChocolateyAvailable,
203
+ runAllVerifications
204
+ };