@fredlackey/devutils 0.0.1

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.
Files changed (200) hide show
  1. package/README.md +156 -0
  2. package/bin/dev.js +16 -0
  3. package/files/README.md +0 -0
  4. package/files/claude/.claude/commands/setup-context.md +3 -0
  5. package/files/monorepos/_archive/README.md +36 -0
  6. package/files/monorepos/_legacy/README.md +36 -0
  7. package/files/monorepos/ai-docs/README.md +33 -0
  8. package/files/monorepos/apps/README.md +24 -0
  9. package/files/monorepos/docs/README.md +40 -0
  10. package/files/monorepos/packages/README.md +25 -0
  11. package/files/monorepos/research/README.md +29 -0
  12. package/files/monorepos/scripts/README.md +24 -0
  13. package/package.json +39 -0
  14. package/src/cli.js +68 -0
  15. package/src/commands/README.md +41 -0
  16. package/src/commands/configure.js +199 -0
  17. package/src/commands/identity.js +1630 -0
  18. package/src/commands/ignore.js +247 -0
  19. package/src/commands/install.js +173 -0
  20. package/src/commands/setup.js +212 -0
  21. package/src/commands/status.js +223 -0
  22. package/src/completion.js +284 -0
  23. package/src/constants.js +45 -0
  24. package/src/ignore/claude-code.txt +10 -0
  25. package/src/ignore/docker.txt +18 -0
  26. package/src/ignore/linux.txt +23 -0
  27. package/src/ignore/macos.txt +36 -0
  28. package/src/ignore/node.txt +55 -0
  29. package/src/ignore/terraform.txt +37 -0
  30. package/src/ignore/vscode.txt +18 -0
  31. package/src/ignore/windows.txt +35 -0
  32. package/src/index.js +0 -0
  33. package/src/installs/README.md +399 -0
  34. package/src/installs/adobe-creative-cloud.js +44 -0
  35. package/src/installs/appcleaner.js +44 -0
  36. package/src/installs/atomicparsley.js +44 -0
  37. package/src/installs/aws-cli.js +44 -0
  38. package/src/installs/balena-etcher.js +44 -0
  39. package/src/installs/bambu-studio.js +44 -0
  40. package/src/installs/bash-completion.js +44 -0
  41. package/src/installs/bash.js +44 -0
  42. package/src/installs/beyond-compare.js +44 -0
  43. package/src/installs/build-essential.js +44 -0
  44. package/src/installs/caffeine.js +44 -0
  45. package/src/installs/camtasia.js +44 -0
  46. package/src/installs/chatgpt.js +44 -0
  47. package/src/installs/chrome-canary.js +44 -0
  48. package/src/installs/chromium.js +44 -0
  49. package/src/installs/claude-code.js +44 -0
  50. package/src/installs/curl.js +44 -0
  51. package/src/installs/cursor.js +44 -0
  52. package/src/installs/dbschema.js +44 -0
  53. package/src/installs/docker.js +44 -0
  54. package/src/installs/drawio.js +44 -0
  55. package/src/installs/elmedia-player.js +44 -0
  56. package/src/installs/ffmpeg.js +44 -0
  57. package/src/installs/gemini-cli.js +44 -0
  58. package/src/installs/git.js +44 -0
  59. package/src/installs/gitego.js +44 -0
  60. package/src/installs/go.js +44 -0
  61. package/src/installs/google-chrome.js +44 -0
  62. package/src/installs/gpg.js +141 -0
  63. package/src/installs/homebrew.js +44 -0
  64. package/src/installs/imageoptim.js +44 -0
  65. package/src/installs/jq.js +44 -0
  66. package/src/installs/keyboard-maestro.js +44 -0
  67. package/src/installs/latex.js +44 -0
  68. package/src/installs/lftp.js +44 -0
  69. package/src/installs/messenger.js +44 -0
  70. package/src/installs/microsoft-office.js +44 -0
  71. package/src/installs/microsoft-teams.js +44 -0
  72. package/src/installs/node.js +44 -0
  73. package/src/installs/nordpass.js +44 -0
  74. package/src/installs/nvm.js +44 -0
  75. package/src/installs/openssh.js +134 -0
  76. package/src/installs/pandoc.js +44 -0
  77. package/src/installs/pinentry.js +44 -0
  78. package/src/installs/pngyu.js +44 -0
  79. package/src/installs/postman.js +44 -0
  80. package/src/installs/safari-tech-preview.js +44 -0
  81. package/src/installs/sfnt2woff.js +44 -0
  82. package/src/installs/shellcheck.js +44 -0
  83. package/src/installs/slack.js +44 -0
  84. package/src/installs/snagit.js +44 -0
  85. package/src/installs/spotify.js +44 -0
  86. package/src/installs/studio-3t.js +44 -0
  87. package/src/installs/sublime-text.js +44 -0
  88. package/src/installs/superwhisper.js +44 -0
  89. package/src/installs/tailscale.js +44 -0
  90. package/src/installs/termius.js +44 -0
  91. package/src/installs/terraform.js +44 -0
  92. package/src/installs/tidal.js +44 -0
  93. package/src/installs/tmux.js +44 -0
  94. package/src/installs/tree.js +44 -0
  95. package/src/installs/vim.js +44 -0
  96. package/src/installs/vlc.js +44 -0
  97. package/src/installs/vscode.js +44 -0
  98. package/src/installs/whatsapp.js +44 -0
  99. package/src/installs/woff2.js +44 -0
  100. package/src/installs/xcode.js +44 -0
  101. package/src/installs/yarn.js +44 -0
  102. package/src/installs/yq.js +44 -0
  103. package/src/installs/yt-dlp.js +44 -0
  104. package/src/installs/zoom.js +44 -0
  105. package/src/scripts/README.md +95 -0
  106. package/src/scripts/afk.js +23 -0
  107. package/src/scripts/backup-all.js +24 -0
  108. package/src/scripts/backup-source.js +24 -0
  109. package/src/scripts/brewd.js +23 -0
  110. package/src/scripts/brewi.js +24 -0
  111. package/src/scripts/brewr.js +24 -0
  112. package/src/scripts/brews.js +24 -0
  113. package/src/scripts/brewu.js +23 -0
  114. package/src/scripts/c.js +23 -0
  115. package/src/scripts/ccurl.js +24 -0
  116. package/src/scripts/certbot-crontab-init.js +24 -0
  117. package/src/scripts/certbot-init.js +25 -0
  118. package/src/scripts/ch.js +23 -0
  119. package/src/scripts/claude-danger.js +23 -0
  120. package/src/scripts/clean-dev.js +24 -0
  121. package/src/scripts/clear-dns-cache.js +23 -0
  122. package/src/scripts/clone.js +25 -0
  123. package/src/scripts/code-all.js +24 -0
  124. package/src/scripts/count-files.js +24 -0
  125. package/src/scripts/count-folders.js +24 -0
  126. package/src/scripts/count.js +24 -0
  127. package/src/scripts/d.js +23 -0
  128. package/src/scripts/datauri.js +24 -0
  129. package/src/scripts/delete-files.js +24 -0
  130. package/src/scripts/docker-clean.js +24 -0
  131. package/src/scripts/dp.js +23 -0
  132. package/src/scripts/e.js +24 -0
  133. package/src/scripts/empty-trash.js +23 -0
  134. package/src/scripts/evm.js +25 -0
  135. package/src/scripts/fetch-github-repos.js +25 -0
  136. package/src/scripts/get-channel.js +24 -0
  137. package/src/scripts/get-course.js +26 -0
  138. package/src/scripts/get-dependencies.js +25 -0
  139. package/src/scripts/get-folder.js +26 -0
  140. package/src/scripts/get-tunes.js +25 -0
  141. package/src/scripts/get-video.js +24 -0
  142. package/src/scripts/git-backup.js +25 -0
  143. package/src/scripts/git-clone.js +25 -0
  144. package/src/scripts/git-pup.js +23 -0
  145. package/src/scripts/git-push.js +24 -0
  146. package/src/scripts/h.js +24 -0
  147. package/src/scripts/hide-desktop-icons.js +23 -0
  148. package/src/scripts/hide-hidden-files.js +23 -0
  149. package/src/scripts/install-dependencies-from.js +25 -0
  150. package/src/scripts/ips.js +26 -0
  151. package/src/scripts/iso.js +24 -0
  152. package/src/scripts/killni.js +23 -0
  153. package/src/scripts/ll.js +24 -0
  154. package/src/scripts/local-ip.js +23 -0
  155. package/src/scripts/m.js +24 -0
  156. package/src/scripts/map.js +24 -0
  157. package/src/scripts/mkd.js +24 -0
  158. package/src/scripts/ncu-update-all.js +24 -0
  159. package/src/scripts/nginx-init.js +28 -0
  160. package/src/scripts/npmi.js +23 -0
  161. package/src/scripts/o.js +24 -0
  162. package/src/scripts/org-by-date.js +24 -0
  163. package/src/scripts/p.js +23 -0
  164. package/src/scripts/packages.js +25 -0
  165. package/src/scripts/path.js +23 -0
  166. package/src/scripts/ports.js +23 -0
  167. package/src/scripts/q.js +23 -0
  168. package/src/scripts/refresh-files.js +26 -0
  169. package/src/scripts/remove-smaller-files.js +24 -0
  170. package/src/scripts/rename-files-with-date.js +25 -0
  171. package/src/scripts/resize-image.js +25 -0
  172. package/src/scripts/rm-safe.js +24 -0
  173. package/src/scripts/s.js +24 -0
  174. package/src/scripts/set-git-public.js +23 -0
  175. package/src/scripts/show-desktop-icons.js +23 -0
  176. package/src/scripts/show-hidden-files.js +23 -0
  177. package/src/scripts/tpa.js +23 -0
  178. package/src/scripts/tpo.js +23 -0
  179. package/src/scripts/u.js +23 -0
  180. package/src/scripts/vpush.js +23 -0
  181. package/src/scripts/y.js +23 -0
  182. package/src/utils/README.md +95 -0
  183. package/src/utils/common/apps.js +143 -0
  184. package/src/utils/common/display.js +157 -0
  185. package/src/utils/common/network.js +185 -0
  186. package/src/utils/common/os.js +202 -0
  187. package/src/utils/common/package-manager.js +301 -0
  188. package/src/utils/common/privileges.js +138 -0
  189. package/src/utils/common/shell.js +195 -0
  190. package/src/utils/macos/apps.js +228 -0
  191. package/src/utils/macos/brew.js +315 -0
  192. package/src/utils/ubuntu/apt.js +301 -0
  193. package/src/utils/ubuntu/desktop.js +292 -0
  194. package/src/utils/ubuntu/snap.js +302 -0
  195. package/src/utils/ubuntu/systemd.js +286 -0
  196. package/src/utils/windows/choco.js +327 -0
  197. package/src/utils/windows/env.js +246 -0
  198. package/src/utils/windows/registry.js +269 -0
  199. package/src/utils/windows/shell.js +240 -0
  200. package/src/utils/windows/winget.js +378 -0
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @fileoverview Ignore command - Append technology-specific patterns to .gitignore.
5
+ * Reads patterns from src/ignore/*.txt and adds them to the repository's .gitignore.
6
+ */
7
+
8
+ const { Command } = require('commander');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ const IGNORE_DIR = path.join(__dirname, '..', 'ignore');
13
+
14
+ /**
15
+ * Get list of available ignore technologies
16
+ * @returns {string[]} Array of technology names
17
+ */
18
+ function getAvailableTechnologies() {
19
+ try {
20
+ if (!fs.existsSync(IGNORE_DIR)) {
21
+ return [];
22
+ }
23
+ return fs.readdirSync(IGNORE_DIR)
24
+ .filter(file => file.endsWith('.txt'))
25
+ .map(file => file.replace('.txt', ''))
26
+ .sort();
27
+ } catch {
28
+ return [];
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Find the git root directory by walking up the tree
34
+ * @param {string} startDir - Starting directory
35
+ * @returns {string|null} Path to git root or null
36
+ */
37
+ function findGitRoot(startDir) {
38
+ let currentDir = path.resolve(startDir);
39
+ const root = path.parse(currentDir).root;
40
+
41
+ while (currentDir !== root) {
42
+ const gitDir = path.join(currentDir, '.git');
43
+ if (fs.existsSync(gitDir)) {
44
+ return currentDir;
45
+ }
46
+ currentDir = path.dirname(currentDir);
47
+ }
48
+
49
+ return null;
50
+ }
51
+
52
+ /**
53
+ * Generate section markers for a technology
54
+ * @param {string} technology - Technology name
55
+ * @returns {{ start: string, end: string }}
56
+ */
57
+ function getSectionMarkers(technology) {
58
+ return {
59
+ start: `# === @fredlackey/devutils: ${technology} ===`,
60
+ end: `# === end: ${technology} ===`
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Check if patterns for a technology are already in .gitignore
66
+ * @param {string} gitignoreContent - Content of .gitignore
67
+ * @param {string} technology - Technology name
68
+ * @returns {boolean}
69
+ */
70
+ function hasPatterns(gitignoreContent, technology) {
71
+ const markers = getSectionMarkers(technology);
72
+ return gitignoreContent.includes(markers.start);
73
+ }
74
+
75
+ /**
76
+ * Remove existing patterns for a technology from .gitignore
77
+ * @param {string} gitignoreContent - Content of .gitignore
78
+ * @param {string} technology - Technology name
79
+ * @returns {string} Updated content
80
+ */
81
+ function removePatterns(gitignoreContent, technology) {
82
+ const markers = getSectionMarkers(technology);
83
+ const lines = gitignoreContent.split('\n');
84
+ const result = [];
85
+ let inSection = false;
86
+
87
+ for (const line of lines) {
88
+ if (line.trim() === markers.start) {
89
+ inSection = true;
90
+ continue;
91
+ }
92
+ if (line.trim() === markers.end) {
93
+ inSection = false;
94
+ continue;
95
+ }
96
+ if (!inSection) {
97
+ result.push(line);
98
+ }
99
+ }
100
+
101
+ // Remove trailing empty lines that were before the section
102
+ while (result.length > 0 && result[result.length - 1] === '') {
103
+ result.pop();
104
+ }
105
+
106
+ return result.join('\n');
107
+ }
108
+
109
+ /**
110
+ * List all available technologies
111
+ */
112
+ function listTechnologies() {
113
+ const technologies = getAvailableTechnologies();
114
+
115
+ if (technologies.length === 0) {
116
+ console.log('\nNo ignore patterns available.');
117
+ console.log(`Pattern files should be placed in: ${IGNORE_DIR}\n`);
118
+ return;
119
+ }
120
+
121
+ console.log('\nAvailable technologies:');
122
+ console.log('─'.repeat(40));
123
+
124
+ for (const tech of technologies) {
125
+ console.log(` ${tech}`);
126
+ }
127
+
128
+ console.log(`\nUsage: dev ignore <technology> [folder]`);
129
+ console.log('');
130
+ }
131
+
132
+ /**
133
+ * Run the ignore command
134
+ * @param {string} technology - Technology name
135
+ * @param {string} folder - Optional folder path for .gitignore
136
+ * @param {object} options - Command options
137
+ */
138
+ async function runIgnore(technology, folder, options) {
139
+ // Handle --list option
140
+ if (options.list) {
141
+ listTechnologies();
142
+ return;
143
+ }
144
+
145
+ // Validate technology argument
146
+ if (!technology) {
147
+ console.error('\nError: No technology specified.');
148
+ console.log('Usage: dev ignore <technology> [folder]');
149
+ console.log('Run `dev ignore --list` to see available options.\n');
150
+ process.exit(1);
151
+ }
152
+
153
+ // Check if technology pattern file exists
154
+ const patternFile = path.join(IGNORE_DIR, `${technology}.txt`);
155
+ if (!fs.existsSync(patternFile)) {
156
+ console.error(`\nError: Unknown technology "${technology}".`);
157
+ console.log('Run `dev ignore --list` to see available options.\n');
158
+ process.exit(1);
159
+ }
160
+
161
+ // Determine target directory for .gitignore
162
+ let targetDir;
163
+ if (folder) {
164
+ // Use specified folder path
165
+ targetDir = path.resolve(folder);
166
+ if (!fs.existsSync(targetDir)) {
167
+ console.error(`\nError: Folder "${folder}" does not exist.\n`);
168
+ process.exit(1);
169
+ }
170
+ if (!fs.statSync(targetDir).isDirectory()) {
171
+ console.error(`\nError: "${folder}" is not a directory.\n`);
172
+ process.exit(1);
173
+ }
174
+ } else {
175
+ // Find git root (default behavior)
176
+ targetDir = findGitRoot(process.cwd());
177
+ if (!targetDir) {
178
+ console.error('\nError: No git repository found.');
179
+ console.log('Initialize with `git init` or specify a folder path.\n');
180
+ process.exit(1);
181
+ }
182
+ }
183
+
184
+ const gitignorePath = path.join(targetDir, '.gitignore');
185
+
186
+ // Read existing .gitignore content
187
+ let gitignoreContent = '';
188
+ if (fs.existsSync(gitignorePath)) {
189
+ gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
190
+ }
191
+
192
+ // Check for existing patterns
193
+ if (hasPatterns(gitignoreContent, technology)) {
194
+ if (options.force) {
195
+ // Remove existing patterns to replace them
196
+ gitignoreContent = removePatterns(gitignoreContent, technology);
197
+ console.log(`Replacing existing patterns for ${technology}...`);
198
+ } else {
199
+ console.log(`\nPatterns for ${technology} already present in .gitignore`);
200
+ console.log('Use --force to replace existing patterns.\n');
201
+ return;
202
+ }
203
+ }
204
+
205
+ // Read pattern file
206
+ const patterns = fs.readFileSync(patternFile, 'utf8');
207
+ const markers = getSectionMarkers(technology);
208
+
209
+ // Build the section to add
210
+ const section = `\n${markers.start}\n${patterns.trim()}\n${markers.end}\n`;
211
+
212
+ // Show dry run output
213
+ if (options.dryRun) {
214
+ console.log(`\n[Dry run] Would append to ${gitignorePath}:`);
215
+ console.log('─'.repeat(40));
216
+ console.log(section);
217
+ console.log('─'.repeat(40));
218
+ return;
219
+ }
220
+
221
+ // Append to .gitignore
222
+ const newContent = gitignoreContent.trimEnd() + section;
223
+ fs.writeFileSync(gitignorePath, newContent);
224
+
225
+ console.log(`\nAdded ${technology} patterns to ${gitignorePath}`);
226
+
227
+ // Show what was added (if verbose)
228
+ if (options.verbose) {
229
+ console.log('\nPatterns added:');
230
+ console.log('─'.repeat(40));
231
+ console.log(patterns.trim());
232
+ console.log('');
233
+ }
234
+ }
235
+
236
+ // Create and configure the command
237
+ const ignore = new Command('ignore')
238
+ .description('Append technology-specific patterns to .gitignore')
239
+ .argument('[technology]', 'Technology name (e.g., node, python, macos)')
240
+ .argument('[folder]', 'Target folder for .gitignore (defaults to git root)')
241
+ .option('--list', 'List all available technologies')
242
+ .option('--dry-run', 'Show what would be added without modifying files')
243
+ .option('--force', 'Re-add patterns even if section already exists')
244
+ .option('--verbose', 'Show detailed output')
245
+ .action(runIgnore);
246
+
247
+ module.exports = ignore;
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @fileoverview Install command - Platform-agnostic installation of development tools.
5
+ * Locates and executes install scripts from src/installs/.
6
+ */
7
+
8
+ const { Command } = require('commander');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const readline = require('readline');
12
+
13
+ const INSTALLS_DIR = path.join(__dirname, '..', 'installs');
14
+
15
+ /**
16
+ * Get list of available install scripts
17
+ * @returns {string[]} Array of install script names
18
+ */
19
+ function getAvailableInstalls() {
20
+ try {
21
+ if (!fs.existsSync(INSTALLS_DIR)) {
22
+ return [];
23
+ }
24
+ return fs.readdirSync(INSTALLS_DIR)
25
+ .filter(file => file.endsWith('.js'))
26
+ .map(file => file.replace('.js', ''))
27
+ .sort();
28
+ } catch {
29
+ return [];
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Prompt user for confirmation
35
+ * @param {string} question - Question to ask
36
+ * @returns {Promise<boolean>}
37
+ */
38
+ function confirm(question) {
39
+ return new Promise(resolve => {
40
+ const rl = readline.createInterface({
41
+ input: process.stdin,
42
+ output: process.stdout
43
+ });
44
+ rl.question(`${question} (y/n): `, answer => {
45
+ rl.close();
46
+ resolve(answer.toLowerCase().startsWith('y'));
47
+ });
48
+ });
49
+ }
50
+
51
+ /**
52
+ * List all available install scripts
53
+ */
54
+ function listInstalls() {
55
+ const installs = getAvailableInstalls();
56
+
57
+ if (installs.length === 0) {
58
+ console.log('\nNo install scripts available.');
59
+ console.log(`Install scripts should be placed in: ${INSTALLS_DIR}\n`);
60
+ return;
61
+ }
62
+
63
+ console.log('\nAvailable install scripts:');
64
+ console.log('─'.repeat(40));
65
+
66
+ for (const name of installs) {
67
+ console.log(` ${name}`);
68
+ }
69
+
70
+ console.log(`\nUsage: dev install <name>`);
71
+ console.log('');
72
+ }
73
+
74
+ /**
75
+ * Run an install script
76
+ * @param {string} name - Name of the install script
77
+ * @param {object} options - Command options
78
+ */
79
+ async function runInstall(name, options) {
80
+ if (!name) {
81
+ console.error('\nError: No package specified.');
82
+ console.log('Usage: dev install <name>');
83
+ console.log('Run `dev install --list` to see available options.\n');
84
+ process.exit(1);
85
+ }
86
+
87
+ const scriptPath = path.join(INSTALLS_DIR, `${name}.js`);
88
+
89
+ // Check if install script exists
90
+ if (!fs.existsSync(scriptPath)) {
91
+ console.error(`\nError: Unknown package "${name}".`);
92
+ console.log('Run `dev install --list` to see available options.\n');
93
+ process.exit(1);
94
+ }
95
+
96
+ // Confirm installation (unless --force)
97
+ if (!options.force) {
98
+ console.log(`\nPreparing to install: ${name}`);
99
+
100
+ if (options.dryRun) {
101
+ console.log('[Dry run mode - no changes will be made]\n');
102
+ }
103
+
104
+ const shouldProceed = await confirm('Proceed with installation?');
105
+ if (!shouldProceed) {
106
+ console.log('Installation cancelled.');
107
+ return;
108
+ }
109
+ }
110
+
111
+ try {
112
+ // Load the install script
113
+ const installScript = require(scriptPath);
114
+
115
+ // Check for install function
116
+ if (typeof installScript.install !== 'function') {
117
+ console.error(`\nError: Install script "${name}" does not export an install() function.`);
118
+ process.exit(1);
119
+ }
120
+
121
+ console.log(`\nInstalling ${name}...`);
122
+ if (options.verbose) {
123
+ console.log(`Script: ${scriptPath}`);
124
+ }
125
+
126
+ // Pass options to the install function
127
+ const installOptions = {
128
+ dryRun: options.dryRun || false,
129
+ verbose: options.verbose || false,
130
+ force: options.force || false
131
+ };
132
+
133
+ // Run the install
134
+ await installScript.install(installOptions);
135
+
136
+ if (!options.dryRun) {
137
+ console.log(`\n${name} installation complete.`);
138
+ }
139
+
140
+ } catch (err) {
141
+ console.error(`\nError installing ${name}:`, err.message);
142
+ if (options.verbose) {
143
+ console.error(err.stack);
144
+ }
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Handle the install command
151
+ * @param {string} name - Package name to install
152
+ * @param {object} options - Command options
153
+ */
154
+ async function handleInstall(name, options) {
155
+ if (options.list) {
156
+ listInstalls();
157
+ return;
158
+ }
159
+
160
+ await runInstall(name, options);
161
+ }
162
+
163
+ // Create and configure the command
164
+ const install = new Command('install')
165
+ .description('Platform-agnostic installation of development tools')
166
+ .argument('[name]', 'Name of the package to install')
167
+ .option('--list', 'List all available install scripts')
168
+ .option('--dry-run', 'Show what would be executed without running')
169
+ .option('--force', 'Skip confirmation prompts')
170
+ .option('--verbose', 'Show detailed output during installation')
171
+ .action(handleInstall);
172
+
173
+ module.exports = install;
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @fileoverview Setup command - Install essential tools required for DevUtils CLI to function.
5
+ * This includes git, ssh-keygen, gpg, and other core dependencies.
6
+ */
7
+
8
+ const { Command } = require('commander');
9
+ const readline = require('readline');
10
+
11
+ const shell = require('../utils/common/shell');
12
+ const osUtils = require('../utils/common/os');
13
+
14
+ // Essential tools that DevUtils CLI requires
15
+ const ESSENTIAL_TOOLS = [
16
+ {
17
+ name: 'git',
18
+ command: 'git',
19
+ description: 'Version control system',
20
+ install: 'git'
21
+ },
22
+ {
23
+ name: 'ssh-keygen',
24
+ command: 'ssh-keygen',
25
+ description: 'SSH key generation',
26
+ install: 'openssh'
27
+ },
28
+ {
29
+ name: 'gpg',
30
+ command: 'gpg',
31
+ description: 'GPG encryption and signing',
32
+ install: 'gpg'
33
+ },
34
+ {
35
+ name: 'curl',
36
+ command: 'curl',
37
+ description: 'Data transfer tool',
38
+ install: 'curl'
39
+ }
40
+ ];
41
+
42
+ /**
43
+ * Create readline interface for prompts
44
+ * @returns {readline.Interface}
45
+ */
46
+ function createPrompt() {
47
+ return readline.createInterface({
48
+ input: process.stdin,
49
+ output: process.stdout
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Prompt user for confirmation
55
+ * @param {readline.Interface} rl - Readline interface
56
+ * @param {string} question - Question to ask
57
+ * @returns {Promise<boolean>}
58
+ */
59
+ function confirm(rl, question) {
60
+ return new Promise(resolve => {
61
+ rl.question(`${question} (y/n): `, answer => {
62
+ resolve(answer.toLowerCase().startsWith('y'));
63
+ });
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Check which essential tools are missing
69
+ * @returns {Array<object>} Array of missing tools
70
+ */
71
+ function checkMissingTools() {
72
+ const missing = [];
73
+ for (const tool of ESSENTIAL_TOOLS) {
74
+ if (!shell.commandExists(tool.command)) {
75
+ missing.push(tool);
76
+ }
77
+ }
78
+ return missing;
79
+ }
80
+
81
+ /**
82
+ * Get all tool statuses
83
+ * @returns {Array<{ tool: object, installed: boolean }>}
84
+ */
85
+ function getToolStatuses() {
86
+ return ESSENTIAL_TOOLS.map(tool => ({
87
+ tool,
88
+ installed: shell.commandExists(tool.command)
89
+ }));
90
+ }
91
+
92
+ /**
93
+ * Install a tool using the appropriate installer
94
+ * @param {object} tool - Tool definition
95
+ * @returns {Promise<boolean>} True if installation succeeded
96
+ */
97
+ async function installTool(tool) {
98
+ try {
99
+ const installer = require(`../installs/${tool.install}`);
100
+ if (typeof installer.install === 'function') {
101
+ return await installer.install();
102
+ }
103
+ console.error(` No install function found for ${tool.name}`);
104
+ return false;
105
+ } catch (err) {
106
+ console.error(` Failed to install ${tool.name}: ${err.message}`);
107
+ return false;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Display tool status table
113
+ * @param {Array<{ tool: object, installed: boolean }>} statuses
114
+ */
115
+ function displayToolStatus(statuses) {
116
+ console.log('\nEssential Tools:');
117
+ console.log('─'.repeat(50));
118
+
119
+ for (const { tool, installed } of statuses) {
120
+ const status = installed ? '✓ Installed' : '✗ Missing';
121
+ const statusColor = installed ? status : status;
122
+ console.log(` ${tool.name.padEnd(15)} ${statusColor.padEnd(15)} ${tool.description}`);
123
+ }
124
+ console.log('');
125
+ }
126
+
127
+ /**
128
+ * Run the setup command
129
+ * @param {object} options - Command options
130
+ */
131
+ async function runSetup(options) {
132
+ const platform = osUtils.detect();
133
+
134
+ console.log('\n=== DevUtils CLI Setup ===\n');
135
+ console.log(`Platform: ${platform.type}`);
136
+ console.log(`Package Manager: ${platform.packageManager || 'unknown'}`);
137
+
138
+ // Check tool statuses
139
+ const statuses = getToolStatuses();
140
+ displayToolStatus(statuses);
141
+
142
+ // Find missing tools
143
+ const missing = statuses.filter(s => !s.installed).map(s => s.tool);
144
+
145
+ if (missing.length === 0) {
146
+ console.log('All essential tools are already installed.\n');
147
+ return;
148
+ }
149
+
150
+ console.log(`Missing ${missing.length} tool(s): ${missing.map(t => t.name).join(', ')}\n`);
151
+
152
+ // Confirm installation unless --force
153
+ let shouldInstall = options.force;
154
+ if (!shouldInstall) {
155
+ const rl = createPrompt();
156
+ shouldInstall = await confirm(rl, 'Install missing tools?');
157
+ rl.close();
158
+ }
159
+
160
+ if (!shouldInstall) {
161
+ console.log('Setup cancelled.\n');
162
+ return;
163
+ }
164
+
165
+ // Install each missing tool
166
+ console.log('\nInstalling missing tools...\n');
167
+
168
+ let installed = 0;
169
+ let failed = 0;
170
+
171
+ for (const tool of missing) {
172
+ console.log(`Installing ${tool.name}...`);
173
+ const success = await installTool(tool);
174
+ if (success) {
175
+ installed++;
176
+ console.log(` ${tool.name} installed successfully.\n`);
177
+ } else {
178
+ failed++;
179
+ console.log(` ${tool.name} installation failed.\n`);
180
+ }
181
+ }
182
+
183
+ // Show final status
184
+ console.log('─'.repeat(50));
185
+ console.log(`\nSetup complete: ${installed} installed, ${failed} failed.\n`);
186
+
187
+ // Show updated status
188
+ if (installed > 0) {
189
+ const updatedStatuses = getToolStatuses();
190
+ displayToolStatus(updatedStatuses);
191
+ }
192
+ }
193
+
194
+ // Create and configure the command
195
+ const setup = new Command('setup')
196
+ .description('Install essential tools required for DevUtils CLI')
197
+ .option('--force', 'Install without prompting')
198
+ .option('--check', 'Check tool status without installing')
199
+ .action(async (options) => {
200
+ if (options.check) {
201
+ const statuses = getToolStatuses();
202
+ displayToolStatus(statuses);
203
+ const missing = statuses.filter(s => !s.installed);
204
+ if (missing.length > 0) {
205
+ console.log(`Run 'dev setup' to install missing tools.\n`);
206
+ }
207
+ return;
208
+ }
209
+ await runSetup(options);
210
+ });
211
+
212
+ module.exports = setup;