@fredlackey/devutils 0.0.1 → 0.0.2

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 (257) hide show
  1. package/README.md +5 -5
  2. package/package.json +1 -1
  3. package/src/commands/install.js +374 -36
  4. package/src/installs/adobe-creative-cloud.js +527 -25
  5. package/src/installs/adobe-creative-cloud.md +605 -0
  6. package/src/installs/appcleaner.js +303 -26
  7. package/src/installs/appcleaner.md +699 -0
  8. package/src/installs/apt-transport-https.js +390 -0
  9. package/src/installs/apt-transport-https.md +678 -0
  10. package/src/installs/atomicparsley.js +624 -26
  11. package/src/installs/atomicparsley.md +795 -0
  12. package/src/installs/aws-cli.js +779 -26
  13. package/src/installs/aws-cli.md +727 -0
  14. package/src/installs/balena-etcher.js +688 -26
  15. package/src/installs/balena-etcher.md +761 -0
  16. package/src/installs/bambu-studio.js +912 -26
  17. package/src/installs/bambu-studio.md +780 -0
  18. package/src/installs/bash-completion.js +554 -23
  19. package/src/installs/bash-completion.md +833 -0
  20. package/src/installs/bash.js +399 -26
  21. package/src/installs/bash.md +993 -0
  22. package/src/installs/beyond-compare.js +585 -26
  23. package/src/installs/beyond-compare.md +813 -0
  24. package/src/installs/build-essential.js +511 -26
  25. package/src/installs/build-essential.md +977 -0
  26. package/src/installs/ca-certificates.js +618 -0
  27. package/src/installs/ca-certificates.md +937 -0
  28. package/src/installs/caffeine.js +490 -26
  29. package/src/installs/caffeine.md +839 -0
  30. package/src/installs/camtasia.js +577 -25
  31. package/src/installs/camtasia.md +762 -0
  32. package/src/installs/chatgpt.js +458 -26
  33. package/src/installs/chatgpt.md +814 -0
  34. package/src/installs/chocolatey.js +447 -0
  35. package/src/installs/chocolatey.md +661 -0
  36. package/src/installs/chrome-canary.js +472 -26
  37. package/src/installs/chrome-canary.md +641 -0
  38. package/src/installs/chromium.js +645 -26
  39. package/src/installs/chromium.md +838 -0
  40. package/src/installs/claude-code.js +558 -26
  41. package/src/installs/claude-code.md +1173 -0
  42. package/src/installs/curl.js +361 -26
  43. package/src/installs/curl.md +714 -0
  44. package/src/installs/cursor.js +561 -26
  45. package/src/installs/cursor.md +970 -0
  46. package/src/installs/dbschema.js +674 -26
  47. package/src/installs/dbschema.md +925 -0
  48. package/src/installs/dependencies.md +435 -0
  49. package/src/installs/development-tools.js +600 -0
  50. package/src/installs/development-tools.md +977 -0
  51. package/src/installs/docker.js +1010 -25
  52. package/src/installs/docker.md +1109 -0
  53. package/src/installs/drawio.js +1001 -26
  54. package/src/installs/drawio.md +795 -0
  55. package/src/installs/elmedia-player.js +328 -25
  56. package/src/installs/elmedia-player.md +556 -0
  57. package/src/installs/ffmpeg.js +870 -25
  58. package/src/installs/ffmpeg.md +852 -0
  59. package/src/installs/file.js +464 -0
  60. package/src/installs/file.md +987 -0
  61. package/src/installs/gemini-cli.js +793 -26
  62. package/src/installs/gemini-cli.md +1153 -0
  63. package/src/installs/git.js +382 -26
  64. package/src/installs/git.md +907 -0
  65. package/src/installs/gitego.js +931 -26
  66. package/src/installs/gitego.md +1172 -0
  67. package/src/installs/go.js +913 -26
  68. package/src/installs/go.md +958 -0
  69. package/src/installs/google-chrome.js +801 -25
  70. package/src/installs/google-chrome.md +862 -0
  71. package/src/installs/gpg.js +412 -73
  72. package/src/installs/gpg.md +1056 -0
  73. package/src/installs/homebrew.js +1015 -26
  74. package/src/installs/homebrew.md +988 -0
  75. package/src/installs/imageoptim.js +950 -26
  76. package/src/installs/imageoptim.md +1119 -0
  77. package/src/installs/installers.json +2297 -0
  78. package/src/installs/jq.js +382 -26
  79. package/src/installs/jq.md +809 -0
  80. package/src/installs/keyboard-maestro.js +701 -26
  81. package/src/installs/keyboard-maestro.md +825 -0
  82. package/src/installs/latex.js +771 -26
  83. package/src/installs/latex.md +1095 -0
  84. package/src/installs/lftp.js +338 -26
  85. package/src/installs/lftp.md +907 -0
  86. package/src/installs/lsb-release.js +346 -0
  87. package/src/installs/lsb-release.md +814 -0
  88. package/src/installs/messenger.js +829 -26
  89. package/src/installs/messenger.md +900 -0
  90. package/src/installs/microsoft-office.js +550 -26
  91. package/src/installs/microsoft-office.md +760 -0
  92. package/src/installs/microsoft-teams.js +782 -25
  93. package/src/installs/microsoft-teams.md +886 -0
  94. package/src/installs/node.js +886 -26
  95. package/src/installs/node.md +1153 -0
  96. package/src/installs/nordpass.js +698 -26
  97. package/src/installs/nordpass.md +921 -0
  98. package/src/installs/nvm.js +977 -26
  99. package/src/installs/nvm.md +1057 -0
  100. package/src/installs/openssh.js +734 -64
  101. package/src/installs/openssh.md +1056 -0
  102. package/src/installs/pandoc.js +644 -26
  103. package/src/installs/pandoc.md +1036 -0
  104. package/src/installs/pinentry.js +492 -26
  105. package/src/installs/pinentry.md +1142 -0
  106. package/src/installs/pngyu.js +851 -26
  107. package/src/installs/pngyu.md +896 -0
  108. package/src/installs/postman.js +781 -26
  109. package/src/installs/postman.md +940 -0
  110. package/src/installs/procps.js +425 -0
  111. package/src/installs/procps.md +851 -0
  112. package/src/installs/safari-tech-preview.js +355 -25
  113. package/src/installs/safari-tech-preview.md +533 -0
  114. package/src/installs/sfnt2woff.js +640 -26
  115. package/src/installs/sfnt2woff.md +795 -0
  116. package/src/installs/shellcheck.js +463 -26
  117. package/src/installs/shellcheck.md +1005 -0
  118. package/src/installs/slack.js +722 -25
  119. package/src/installs/slack.md +865 -0
  120. package/src/installs/snagit.js +566 -25
  121. package/src/installs/snagit.md +844 -0
  122. package/src/installs/software-properties-common.js +372 -0
  123. package/src/installs/software-properties-common.md +805 -0
  124. package/src/installs/spotify.js +858 -25
  125. package/src/installs/spotify.md +901 -0
  126. package/src/installs/studio-3t.js +803 -26
  127. package/src/installs/studio-3t.md +918 -0
  128. package/src/installs/sublime-text.js +780 -25
  129. package/src/installs/sublime-text.md +914 -0
  130. package/src/installs/superwhisper.js +687 -25
  131. package/src/installs/superwhisper.md +630 -0
  132. package/src/installs/tailscale.js +727 -26
  133. package/src/installs/tailscale.md +1100 -0
  134. package/src/installs/tar.js +389 -0
  135. package/src/installs/tar.md +946 -0
  136. package/src/installs/termius.js +780 -26
  137. package/src/installs/termius.md +844 -0
  138. package/src/installs/terraform.js +761 -26
  139. package/src/installs/terraform.md +899 -0
  140. package/src/installs/tidal.js +752 -25
  141. package/src/installs/tidal.md +864 -0
  142. package/src/installs/tmux.js +328 -26
  143. package/src/installs/tmux.md +1030 -0
  144. package/src/installs/tree.js +393 -26
  145. package/src/installs/tree.md +833 -0
  146. package/src/installs/unzip.js +460 -0
  147. package/src/installs/unzip.md +879 -0
  148. package/src/installs/vim.js +403 -26
  149. package/src/installs/vim.md +1040 -0
  150. package/src/installs/vlc.js +803 -26
  151. package/src/installs/vlc.md +927 -0
  152. package/src/installs/vscode.js +825 -26
  153. package/src/installs/vscode.md +1002 -0
  154. package/src/installs/wget.js +415 -0
  155. package/src/installs/wget.md +791 -0
  156. package/src/installs/whatsapp.js +710 -25
  157. package/src/installs/whatsapp.md +854 -0
  158. package/src/installs/winpty.js +352 -0
  159. package/src/installs/winpty.md +620 -0
  160. package/src/installs/woff2.js +535 -26
  161. package/src/installs/woff2.md +977 -0
  162. package/src/installs/wsl.js +572 -0
  163. package/src/installs/wsl.md +699 -0
  164. package/src/installs/xcode-clt.js +520 -0
  165. package/src/installs/xcode-clt.md +351 -0
  166. package/src/installs/xcode.js +542 -26
  167. package/src/installs/xcode.md +573 -0
  168. package/src/installs/yarn.js +806 -26
  169. package/src/installs/yarn.md +1074 -0
  170. package/src/installs/yq.js +636 -26
  171. package/src/installs/yq.md +944 -0
  172. package/src/installs/yt-dlp.js +683 -26
  173. package/src/installs/yt-dlp.md +946 -0
  174. package/src/installs/yum-utils.js +297 -0
  175. package/src/installs/yum-utils.md +648 -0
  176. package/src/installs/zoom.js +740 -25
  177. package/src/installs/zoom.md +884 -0
  178. package/src/scripts/README.md +567 -45
  179. package/src/scripts/STATUS.md +208 -0
  180. package/src/scripts/afk.js +395 -7
  181. package/src/scripts/backup-all.js +731 -9
  182. package/src/scripts/backup-source.js +711 -8
  183. package/src/scripts/brewd.js +373 -7
  184. package/src/scripts/brewi.js +505 -9
  185. package/src/scripts/brewr.js +512 -9
  186. package/src/scripts/brews.js +462 -9
  187. package/src/scripts/brewu.js +488 -7
  188. package/src/scripts/c.js +185 -7
  189. package/src/scripts/ccurl.js +325 -8
  190. package/src/scripts/certbot-crontab-init.js +488 -8
  191. package/src/scripts/certbot-init.js +641 -9
  192. package/src/scripts/ch.js +339 -7
  193. package/src/scripts/claude-danger.js +253 -8
  194. package/src/scripts/clean-dev.js +419 -8
  195. package/src/scripts/clear-dns-cache.js +525 -7
  196. package/src/scripts/clone.js +417 -7
  197. package/src/scripts/code-all.js +420 -7
  198. package/src/scripts/count-files.js +195 -8
  199. package/src/scripts/count-folders.js +195 -8
  200. package/src/scripts/count.js +248 -8
  201. package/src/scripts/d.js +203 -7
  202. package/src/scripts/datauri.js +373 -8
  203. package/src/scripts/delete-files.js +363 -7
  204. package/src/scripts/docker-clean.js +410 -8
  205. package/src/scripts/dp.js +426 -7
  206. package/src/scripts/e.js +375 -9
  207. package/src/scripts/empty-trash.js +497 -7
  208. package/src/scripts/evm.js +428 -9
  209. package/src/scripts/fetch-github-repos.js +441 -10
  210. package/src/scripts/get-channel.js +329 -8
  211. package/src/scripts/get-course.js +384 -11
  212. package/src/scripts/get-dependencies.js +290 -9
  213. package/src/scripts/get-folder.js +783 -10
  214. package/src/scripts/get-tunes.js +411 -10
  215. package/src/scripts/get-video.js +352 -9
  216. package/src/scripts/git-backup.js +561 -9
  217. package/src/scripts/git-clone.js +477 -9
  218. package/src/scripts/git-pup.js +303 -7
  219. package/src/scripts/git-push.js +380 -8
  220. package/src/scripts/h.js +607 -9
  221. package/src/scripts/hide-desktop-icons.js +483 -7
  222. package/src/scripts/hide-hidden-files.js +522 -7
  223. package/src/scripts/install-dependencies-from.js +440 -9
  224. package/src/scripts/ips.js +647 -10
  225. package/src/scripts/iso.js +354 -8
  226. package/src/scripts/killni.js +561 -7
  227. package/src/scripts/ll.js +451 -8
  228. package/src/scripts/local-ip.js +310 -8
  229. package/src/scripts/m.js +508 -8
  230. package/src/scripts/map.js +293 -8
  231. package/src/scripts/mkd.js +287 -7
  232. package/src/scripts/ncu-update-all.js +441 -8
  233. package/src/scripts/nginx-init.js +702 -12
  234. package/src/scripts/npmi.js +366 -7
  235. package/src/scripts/o.js +495 -8
  236. package/src/scripts/org-by-date.js +321 -7
  237. package/src/scripts/p.js +208 -7
  238. package/src/scripts/packages.js +313 -8
  239. package/src/scripts/path.js +209 -7
  240. package/src/scripts/ports.js +582 -8
  241. package/src/scripts/q.js +290 -8
  242. package/src/scripts/refresh-files.js +378 -10
  243. package/src/scripts/remove-smaller-files.js +500 -8
  244. package/src/scripts/rename-files-with-date.js +517 -9
  245. package/src/scripts/resize-image.js +523 -9
  246. package/src/scripts/rm-safe.js +653 -8
  247. package/src/scripts/s.js +525 -9
  248. package/src/scripts/set-git-public.js +349 -7
  249. package/src/scripts/show-desktop-icons.js +459 -7
  250. package/src/scripts/show-hidden-files.js +456 -7
  251. package/src/scripts/tpa.js +265 -8
  252. package/src/scripts/tpo.js +264 -7
  253. package/src/scripts/u.js +489 -7
  254. package/src/scripts/vpush.js +422 -8
  255. package/src/scripts/y.js +267 -7
  256. package/src/utils/common/os.js +94 -2
  257. package/src/utils/ubuntu/apt.js +13 -7
@@ -1,24 +1,727 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * @fileoverview Backup ~/Source directory using rsync.
4
+ * backup-source - Backup ~/Source directory using rsync or robocopy.
5
+ *
6
+ * Migrated from legacy dotfiles alias.
7
+ * Original:
8
+ * backup-source(){
9
+ * backupdir="$*$(date +"%Y%m%d%H%M%S")/"
10
+ * backupcmd="rsync -arv --progress --no-links --exclude={.Trash,.android,.atom,...} ~/Source $backupdir"
11
+ * mkdir -p "$backupdir"
12
+ * eval "$backupcmd"
13
+ * cd "$backupdir"
14
+ * }
15
+ *
5
16
  * @module scripts/backup-source
6
17
  */
7
18
 
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const os = require('../utils/common/os');
22
+ const shell = require('../utils/common/shell');
23
+
24
+ /**
25
+ * Directories and files to exclude from backup.
26
+ * These are common development artifacts, caches, and system directories
27
+ * that should not be backed up.
28
+ */
29
+ const EXCLUDE_PATTERNS = [
30
+ '.Trash',
31
+ '.android',
32
+ '.atom',
33
+ '.bash_sessions',
34
+ '.cache',
35
+ '.cups',
36
+ '.dropbox',
37
+ '.git',
38
+ '.next',
39
+ '.npm',
40
+ '.nvm',
41
+ '.viminfo',
42
+ 'bower_components',
43
+ 'node_modules',
44
+ '.tmp',
45
+ '.idea',
46
+ '.DS_Store',
47
+ '.terraform'
48
+ ];
49
+
50
+ /**
51
+ * Generates a timestamp string in YYYYMMDDHHmmss format.
52
+ * This format is used to create unique backup directory names.
53
+ *
54
+ * @returns {string} Timestamp in YYYYMMDDHHmmss format (e.g., "20240115143022")
55
+ */
56
+ function generateTimestamp() {
57
+ const now = new Date();
58
+ const year = now.getFullYear();
59
+ const month = String(now.getMonth() + 1).padStart(2, '0');
60
+ const day = String(now.getDate()).padStart(2, '0');
61
+ const hours = String(now.getHours()).padStart(2, '0');
62
+ const minutes = String(now.getMinutes()).padStart(2, '0');
63
+ const seconds = String(now.getSeconds()).padStart(2, '0');
64
+ return `${year}${month}${day}${hours}${minutes}${seconds}`;
65
+ }
66
+
67
+ /**
68
+ * Gets the path to the user's Source directory.
69
+ * This is typically ~/Source on all platforms.
70
+ *
71
+ * @returns {string} Absolute path to ~/Source
72
+ */
73
+ function getSourceDirectory() {
74
+ return path.join(os.getHomeDir(), 'Source');
75
+ }
76
+
77
+ /**
78
+ * Validates that the Source directory exists.
79
+ *
80
+ * @returns {boolean} True if ~/Source exists and is a directory
81
+ */
82
+ function sourceDirectoryExists() {
83
+ const sourcePath = getSourceDirectory();
84
+ try {
85
+ const stats = fs.statSync(sourcePath);
86
+ return stats.isDirectory();
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Checks if rsync is available on the system.
94
+ *
95
+ * @returns {boolean} True if rsync command is available
96
+ */
97
+ function isRsyncAvailable() {
98
+ return shell.commandExists('rsync');
99
+ }
100
+
101
+ /**
102
+ * Node.js implementation note:
103
+ *
104
+ * This script intentionally uses rsync (macOS/Linux) or robocopy (Windows)
105
+ * instead of a pure Node.js implementation because:
106
+ *
107
+ * 1. rsync is battle-tested for decades with excellent file synchronization
108
+ * 2. rsync handles edge cases (symlinks, permissions, sparse files) that
109
+ * would be complex to implement correctly in Node.js
110
+ * 3. rsync has progress reporting and resumable transfers
111
+ * 4. robocopy is the Windows equivalent with similar benefits
112
+ *
113
+ * A pure Node.js implementation would not be appropriate for this use case.
114
+ *
115
+ * @param {string[]} args - Command line arguments (unused - this is a note)
116
+ * @returns {Promise<void>}
117
+ */
118
+ async function do_backup_source_nodejs(args) {
119
+ // This function documents why we don't use pure Node.js for this task.
120
+ // The platform-specific functions use rsync or robocopy because those tools
121
+ // are superior for file synchronization compared to implementing the same
122
+ // functionality in Node.js.
123
+ throw new Error(
124
+ 'do_backup_source_nodejs should not be called directly. ' +
125
+ 'This script requires platform-specific tools (rsync or robocopy).'
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Backs up ~/Source directory on macOS using rsync.
131
+ *
132
+ * Creates a timestamped backup directory at the specified destination and
133
+ * uses rsync to copy all files while excluding common development artifacts.
134
+ *
135
+ * @param {string[]} args - Command line arguments
136
+ * @param {string} args[0] - Destination backup directory path (required)
137
+ * @returns {Promise<void>}
138
+ */
139
+ async function do_backup_source_macos(args) {
140
+ // Validate destination argument
141
+ if (!args || args.length === 0 || !args[0]) {
142
+ console.error('Error: Destination backup directory is required.');
143
+ console.error('');
144
+ console.error('Usage: backup-source /path/to/backups/');
145
+ console.error('');
146
+ console.error('Example:');
147
+ console.error(' backup-source /Volumes/ExternalDrive/backups/');
148
+ console.error(' backup-source ~/Backups/');
149
+ process.exit(1);
150
+ }
151
+
152
+ // Validate Source directory exists
153
+ if (!sourceDirectoryExists()) {
154
+ console.error('Error: Source directory does not exist.');
155
+ console.error(`Expected path: ${getSourceDirectory()}`);
156
+ console.error('');
157
+ console.error('Create the directory first or check if it exists at a different location.');
158
+ process.exit(1);
159
+ }
160
+
161
+ // Check if rsync is available
162
+ if (!isRsyncAvailable()) {
163
+ console.error('Error: rsync is required but not installed.');
164
+ console.error('Install it with: brew install rsync');
165
+ process.exit(1);
166
+ }
167
+
168
+ // Build the backup directory path with timestamp
169
+ const destBase = args.join(' ').replace(/\/+$/, ''); // Remove trailing slashes
170
+ const timestamp = generateTimestamp();
171
+ const backupDir = `${destBase}${timestamp}/`;
172
+
173
+ // Create the backup directory
174
+ console.log(`Creating backup directory: ${backupDir}`);
175
+ try {
176
+ fs.mkdirSync(backupDir, { recursive: true });
177
+ } catch (err) {
178
+ console.error(`Error: Failed to create backup directory: ${err.message}`);
179
+ process.exit(1);
180
+ }
181
+
182
+ // Build rsync command with exclude patterns
183
+ const excludeArgs = EXCLUDE_PATTERNS.map(pattern => `--exclude="${pattern}"`).join(' ');
184
+ const sourcePath = getSourceDirectory();
185
+ const rsyncCmd = `rsync -arv --progress --no-links ${excludeArgs} "${sourcePath}" "${backupDir}"`;
186
+
187
+ console.log('');
188
+ console.log('Starting backup of ~/Source...');
189
+ console.log(`Source: ${sourcePath}`);
190
+ console.log(`Destination: ${backupDir}`);
191
+ console.log('');
192
+
193
+ // Execute rsync with live output
194
+ const result = await shell.spawnAsync('rsync', [
195
+ '-arv',
196
+ '--progress',
197
+ '--no-links',
198
+ ...EXCLUDE_PATTERNS.map(p => `--exclude=${p}`),
199
+ sourcePath,
200
+ backupDir
201
+ ], {
202
+ onStdout: (data) => process.stdout.write(data),
203
+ onStderr: (data) => process.stderr.write(data)
204
+ });
205
+
206
+ if (result.code !== 0) {
207
+ console.error('');
208
+ console.error('Error: Backup failed.');
209
+ console.error(result.stderr);
210
+ process.exit(1);
211
+ }
212
+
213
+ console.log('');
214
+ console.log('Backup completed successfully!');
215
+ console.log(`Backup location: ${backupDir}`);
216
+ }
217
+
218
+ /**
219
+ * Backs up ~/Source directory on Ubuntu using rsync.
220
+ *
221
+ * Creates a timestamped backup directory at the specified destination and
222
+ * uses rsync to copy all files while excluding common development artifacts.
223
+ *
224
+ * @param {string[]} args - Command line arguments
225
+ * @param {string} args[0] - Destination backup directory path (required)
226
+ * @returns {Promise<void>}
227
+ */
228
+ async function do_backup_source_ubuntu(args) {
229
+ // Validate destination argument
230
+ if (!args || args.length === 0 || !args[0]) {
231
+ console.error('Error: Destination backup directory is required.');
232
+ console.error('');
233
+ console.error('Usage: backup-source /path/to/backups/');
234
+ console.error('');
235
+ console.error('Example:');
236
+ console.error(' backup-source /mnt/backup/');
237
+ console.error(' backup-source ~/Backups/');
238
+ process.exit(1);
239
+ }
240
+
241
+ // Validate Source directory exists
242
+ if (!sourceDirectoryExists()) {
243
+ console.error('Error: Source directory does not exist.');
244
+ console.error(`Expected path: ${getSourceDirectory()}`);
245
+ console.error('');
246
+ console.error('Create the directory first or check if it exists at a different location.');
247
+ process.exit(1);
248
+ }
249
+
250
+ // Check if rsync is available
251
+ if (!isRsyncAvailable()) {
252
+ console.error('Error: rsync is required but not installed.');
253
+ console.error('Install it with: sudo apt-get install rsync');
254
+ process.exit(1);
255
+ }
256
+
257
+ // Build the backup directory path with timestamp
258
+ const destBase = args.join(' ').replace(/\/+$/, ''); // Remove trailing slashes
259
+ const timestamp = generateTimestamp();
260
+ const backupDir = `${destBase}${timestamp}/`;
261
+
262
+ // Create the backup directory
263
+ console.log(`Creating backup directory: ${backupDir}`);
264
+ try {
265
+ fs.mkdirSync(backupDir, { recursive: true });
266
+ } catch (err) {
267
+ console.error(`Error: Failed to create backup directory: ${err.message}`);
268
+ process.exit(1);
269
+ }
270
+
271
+ // Get source path
272
+ const sourcePath = getSourceDirectory();
273
+
274
+ console.log('');
275
+ console.log('Starting backup of ~/Source...');
276
+ console.log(`Source: ${sourcePath}`);
277
+ console.log(`Destination: ${backupDir}`);
278
+ console.log('');
279
+
280
+ // Execute rsync with live output
281
+ const result = await shell.spawnAsync('rsync', [
282
+ '-arv',
283
+ '--progress',
284
+ '--no-links',
285
+ ...EXCLUDE_PATTERNS.map(p => `--exclude=${p}`),
286
+ sourcePath,
287
+ backupDir
288
+ ], {
289
+ onStdout: (data) => process.stdout.write(data),
290
+ onStderr: (data) => process.stderr.write(data)
291
+ });
292
+
293
+ if (result.code !== 0) {
294
+ console.error('');
295
+ console.error('Error: Backup failed.');
296
+ console.error(result.stderr);
297
+ process.exit(1);
298
+ }
299
+
300
+ console.log('');
301
+ console.log('Backup completed successfully!');
302
+ console.log(`Backup location: ${backupDir}`);
303
+ }
304
+
305
+ /**
306
+ * Backs up ~/Source directory on Raspberry Pi OS using rsync.
307
+ *
308
+ * Creates a timestamped backup directory at the specified destination and
309
+ * uses rsync to copy all files while excluding common development artifacts.
310
+ *
311
+ * @param {string[]} args - Command line arguments
312
+ * @param {string} args[0] - Destination backup directory path (required)
313
+ * @returns {Promise<void>}
314
+ */
315
+ async function do_backup_source_raspbian(args) {
316
+ // Raspbian uses the same rsync approach as Ubuntu
317
+ return do_backup_source_ubuntu(args);
318
+ }
319
+
320
+ /**
321
+ * Backs up ~/Source directory on Amazon Linux using rsync.
322
+ *
323
+ * Creates a timestamped backup directory at the specified destination and
324
+ * uses rsync to copy all files while excluding common development artifacts.
325
+ *
326
+ * @param {string[]} args - Command line arguments
327
+ * @param {string} args[0] - Destination backup directory path (required)
328
+ * @returns {Promise<void>}
329
+ */
330
+ async function do_backup_source_amazon_linux(args) {
331
+ // Validate destination argument
332
+ if (!args || args.length === 0 || !args[0]) {
333
+ console.error('Error: Destination backup directory is required.');
334
+ console.error('');
335
+ console.error('Usage: backup-source /path/to/backups/');
336
+ console.error('');
337
+ console.error('Example:');
338
+ console.error(' backup-source /mnt/efs/backups/');
339
+ console.error(' backup-source ~/Backups/');
340
+ process.exit(1);
341
+ }
342
+
343
+ // Validate Source directory exists
344
+ if (!sourceDirectoryExists()) {
345
+ console.error('Error: Source directory does not exist.');
346
+ console.error(`Expected path: ${getSourceDirectory()}`);
347
+ console.error('');
348
+ console.error('Create the directory first or check if it exists at a different location.');
349
+ process.exit(1);
350
+ }
351
+
352
+ // Check if rsync is available
353
+ if (!isRsyncAvailable()) {
354
+ console.error('Error: rsync is required but not installed.');
355
+ console.error('Install it with: sudo dnf install rsync (or: sudo yum install rsync)');
356
+ process.exit(1);
357
+ }
358
+
359
+ // Build the backup directory path with timestamp
360
+ const destBase = args.join(' ').replace(/\/+$/, ''); // Remove trailing slashes
361
+ const timestamp = generateTimestamp();
362
+ const backupDir = `${destBase}${timestamp}/`;
363
+
364
+ // Create the backup directory
365
+ console.log(`Creating backup directory: ${backupDir}`);
366
+ try {
367
+ fs.mkdirSync(backupDir, { recursive: true });
368
+ } catch (err) {
369
+ console.error(`Error: Failed to create backup directory: ${err.message}`);
370
+ process.exit(1);
371
+ }
372
+
373
+ // Get source path
374
+ const sourcePath = getSourceDirectory();
375
+
376
+ console.log('');
377
+ console.log('Starting backup of ~/Source...');
378
+ console.log(`Source: ${sourcePath}`);
379
+ console.log(`Destination: ${backupDir}`);
380
+ console.log('');
381
+
382
+ // Execute rsync with live output
383
+ const result = await shell.spawnAsync('rsync', [
384
+ '-arv',
385
+ '--progress',
386
+ '--no-links',
387
+ ...EXCLUDE_PATTERNS.map(p => `--exclude=${p}`),
388
+ sourcePath,
389
+ backupDir
390
+ ], {
391
+ onStdout: (data) => process.stdout.write(data),
392
+ onStderr: (data) => process.stderr.write(data)
393
+ });
394
+
395
+ if (result.code !== 0) {
396
+ console.error('');
397
+ console.error('Error: Backup failed.');
398
+ console.error(result.stderr);
399
+ process.exit(1);
400
+ }
401
+
402
+ console.log('');
403
+ console.log('Backup completed successfully!');
404
+ console.log(`Backup location: ${backupDir}`);
405
+ }
406
+
407
+ /**
408
+ * Backs up ~/Source directory on Windows Command Prompt using robocopy.
409
+ *
410
+ * Creates a timestamped backup directory at the specified destination and
411
+ * uses robocopy (built into Windows) to copy all files while excluding
412
+ * common development artifacts.
413
+ *
414
+ * @param {string[]} args - Command line arguments
415
+ * @param {string} args[0] - Destination backup directory path (required)
416
+ * @returns {Promise<void>}
417
+ */
418
+ async function do_backup_source_cmd(args) {
419
+ // Validate destination argument
420
+ if (!args || args.length === 0 || !args[0]) {
421
+ console.error('Error: Destination backup directory is required.');
422
+ console.error('');
423
+ console.error('Usage: backup-source C:\\path\\to\\backups\\');
424
+ console.error('');
425
+ console.error('Example:');
426
+ console.error(' backup-source D:\\Backups\\');
427
+ console.error(' backup-source %USERPROFILE%\\Backups\\');
428
+ process.exit(1);
429
+ }
430
+
431
+ // Validate Source directory exists
432
+ if (!sourceDirectoryExists()) {
433
+ console.error('Error: Source directory does not exist.');
434
+ console.error(`Expected path: ${getSourceDirectory()}`);
435
+ console.error('');
436
+ console.error('Create the directory first or check if it exists at a different location.');
437
+ process.exit(1);
438
+ }
439
+
440
+ // Build the backup directory path with timestamp
441
+ const destBase = args.join(' ').replace(/\\+$/, '').replace(/\/+$/, ''); // Remove trailing slashes
442
+ const timestamp = generateTimestamp();
443
+ const backupDir = path.join(destBase + timestamp);
444
+
445
+ // Create the backup directory
446
+ console.log(`Creating backup directory: ${backupDir}`);
447
+ try {
448
+ fs.mkdirSync(backupDir, { recursive: true });
449
+ } catch (err) {
450
+ console.error(`Error: Failed to create backup directory: ${err.message}`);
451
+ process.exit(1);
452
+ }
453
+
454
+ // Get source path
455
+ const sourcePath = getSourceDirectory();
456
+ const sourceBasename = path.basename(sourcePath);
457
+ const destPath = path.join(backupDir, sourceBasename);
458
+
459
+ console.log('');
460
+ console.log('Starting backup of ~/Source...');
461
+ console.log(`Source: ${sourcePath}`);
462
+ console.log(`Destination: ${destPath}`);
463
+ console.log('');
464
+
465
+ // Build robocopy command with exclude patterns
466
+ // robocopy uses /XD for directories and /XF for files
467
+ const excludeDirs = EXCLUDE_PATTERNS.filter(p => !p.includes('.')).map(p => `/XD "${p}"`).join(' ');
468
+ const excludeFiles = EXCLUDE_PATTERNS.filter(p => p.includes('.')).map(p => `/XF "${p}"`).join(' ');
469
+
470
+ // Execute robocopy
471
+ // /E = copy subdirectories including empty ones
472
+ // /R:3 = retry 3 times on failed copies
473
+ // /W:5 = wait 5 seconds between retries
474
+ // /NP = no progress (we'll use /V for verbose instead)
475
+ // /XJ = exclude junction points (similar to --no-links)
476
+ const robocopyArgs = [
477
+ sourcePath,
478
+ destPath,
479
+ '/E',
480
+ '/R:3',
481
+ '/W:5',
482
+ '/XJ',
483
+ ...EXCLUDE_PATTERNS.map(p => `/XD ${p}`),
484
+ ...EXCLUDE_PATTERNS.map(p => `/XF ${p}`)
485
+ ];
486
+
487
+ const result = await shell.spawnAsync('robocopy', robocopyArgs, {
488
+ onStdout: (data) => process.stdout.write(data),
489
+ onStderr: (data) => process.stderr.write(data)
490
+ });
491
+
492
+ // robocopy exit codes: 0-7 are success (0=no files copied, 1=files copied, etc.)
493
+ // Exit code 8+ indicates errors
494
+ if (result.code >= 8) {
495
+ console.error('');
496
+ console.error('Error: Backup failed.');
497
+ console.error(result.stderr);
498
+ process.exit(1);
499
+ }
500
+
501
+ console.log('');
502
+ console.log('Backup completed successfully!');
503
+ console.log(`Backup location: ${backupDir}`);
504
+ }
505
+
506
+ /**
507
+ * Backs up ~/Source directory on Windows PowerShell using robocopy.
508
+ *
509
+ * Creates a timestamped backup directory at the specified destination and
510
+ * uses robocopy (built into Windows) to copy all files while excluding
511
+ * common development artifacts.
512
+ *
513
+ * @param {string[]} args - Command line arguments
514
+ * @param {string} args[0] - Destination backup directory path (required)
515
+ * @returns {Promise<void>}
516
+ */
517
+ async function do_backup_source_powershell(args) {
518
+ // PowerShell uses the same robocopy approach as CMD
519
+ return do_backup_source_cmd(args);
520
+ }
521
+
522
+ /**
523
+ * Backs up ~/Source directory in Git Bash using rsync (if available) or robocopy.
524
+ *
525
+ * Git Bash runs on Windows but can use rsync if it's installed via MSYS2.
526
+ * Falls back to robocopy via PowerShell if rsync is not available.
527
+ *
528
+ * @param {string[]} args - Command line arguments
529
+ * @param {string} args[0] - Destination backup directory path (required)
530
+ * @returns {Promise<void>}
531
+ */
532
+ async function do_backup_source_gitbash(args) {
533
+ // Validate destination argument
534
+ if (!args || args.length === 0 || !args[0]) {
535
+ console.error('Error: Destination backup directory is required.');
536
+ console.error('');
537
+ console.error('Usage: backup-source /path/to/backups/');
538
+ console.error('');
539
+ console.error('Example:');
540
+ console.error(' backup-source /d/Backups/');
541
+ console.error(' backup-source ~/Backups/');
542
+ process.exit(1);
543
+ }
544
+
545
+ // Validate Source directory exists
546
+ if (!sourceDirectoryExists()) {
547
+ console.error('Error: Source directory does not exist.');
548
+ console.error(`Expected path: ${getSourceDirectory()}`);
549
+ console.error('');
550
+ console.error('Create the directory first or check if it exists at a different location.');
551
+ process.exit(1);
552
+ }
553
+
554
+ // Check if rsync is available in Git Bash
555
+ if (isRsyncAvailable()) {
556
+ // Build the backup directory path with timestamp
557
+ const destBase = args.join(' ').replace(/\/+$/, ''); // Remove trailing slashes
558
+ const timestamp = generateTimestamp();
559
+ const backupDir = `${destBase}${timestamp}/`;
560
+
561
+ // Create the backup directory
562
+ console.log(`Creating backup directory: ${backupDir}`);
563
+ try {
564
+ fs.mkdirSync(backupDir, { recursive: true });
565
+ } catch (err) {
566
+ console.error(`Error: Failed to create backup directory: ${err.message}`);
567
+ process.exit(1);
568
+ }
569
+
570
+ // Get source path
571
+ const sourcePath = getSourceDirectory();
572
+
573
+ console.log('');
574
+ console.log('Starting backup of ~/Source using rsync...');
575
+ console.log(`Source: ${sourcePath}`);
576
+ console.log(`Destination: ${backupDir}`);
577
+ console.log('');
578
+
579
+ // Execute rsync with live output
580
+ const result = await shell.spawnAsync('rsync', [
581
+ '-arv',
582
+ '--progress',
583
+ '--no-links',
584
+ ...EXCLUDE_PATTERNS.map(p => `--exclude=${p}`),
585
+ sourcePath,
586
+ backupDir
587
+ ], {
588
+ onStdout: (data) => process.stdout.write(data),
589
+ onStderr: (data) => process.stderr.write(data)
590
+ });
591
+
592
+ if (result.code !== 0) {
593
+ console.error('');
594
+ console.error('Error: Backup failed.');
595
+ console.error(result.stderr);
596
+ process.exit(1);
597
+ }
598
+
599
+ console.log('');
600
+ console.log('Backup completed successfully!');
601
+ console.log(`Backup location: ${backupDir}`);
602
+ } else {
603
+ // Fall back to robocopy via PowerShell
604
+ console.log('rsync not found in Git Bash, using robocopy via PowerShell...');
605
+ console.log('');
606
+
607
+ // Convert Git Bash paths to Windows paths for robocopy
608
+ const destBase = args.join(' ').replace(/\/+$/, '');
609
+ const timestamp = generateTimestamp();
610
+
611
+ // Convert /c/Users/... to C:\Users\... for PowerShell
612
+ let windowsDestBase = destBase;
613
+ if (destBase.match(/^\/[a-zA-Z]\//)) {
614
+ // Git Bash style path like /c/Users/...
615
+ windowsDestBase = destBase.replace(/^\/([a-zA-Z])\//, '$1:\\').replace(/\//g, '\\');
616
+ }
617
+
618
+ const backupDir = windowsDestBase + timestamp;
619
+
620
+ // Create the backup directory
621
+ console.log(`Creating backup directory: ${backupDir}`);
622
+ try {
623
+ fs.mkdirSync(backupDir, { recursive: true });
624
+ } catch (err) {
625
+ console.error(`Error: Failed to create backup directory: ${err.message}`);
626
+ process.exit(1);
627
+ }
628
+
629
+ // Get source path and convert to Windows format
630
+ const sourcePath = getSourceDirectory();
631
+ let windowsSourcePath = sourcePath;
632
+ if (sourcePath.match(/^\/[a-zA-Z]\//)) {
633
+ windowsSourcePath = sourcePath.replace(/^\/([a-zA-Z])\//, '$1:\\').replace(/\//g, '\\');
634
+ }
635
+
636
+ const sourceBasename = path.basename(sourcePath);
637
+ const destPath = path.join(backupDir, sourceBasename);
638
+
639
+ console.log('');
640
+ console.log('Starting backup of ~/Source using robocopy...');
641
+ console.log(`Source: ${windowsSourcePath}`);
642
+ console.log(`Destination: ${destPath}`);
643
+ console.log('');
644
+
645
+ // Build robocopy command for PowerShell
646
+ const excludeArgs = EXCLUDE_PATTERNS.map(p => `/XD "${p}" /XF "${p}"`).join(' ');
647
+ const robocopyCmd = `robocopy "${windowsSourcePath}" "${destPath}" /E /R:3 /W:5 /XJ ${excludeArgs}`;
648
+
649
+ const result = await shell.exec(`powershell.exe -NoProfile -Command "${robocopyCmd}"`);
650
+
651
+ // robocopy exit codes: 0-7 are success
652
+ // Note: shell.exec returns the exit code differently than spawnAsync
653
+ // We check stderr for errors since robocopy outputs to stdout
654
+ if (result.code >= 8) {
655
+ console.error('');
656
+ console.error('Error: Backup failed.');
657
+ console.error(result.stderr || result.stdout);
658
+ process.exit(1);
659
+ }
660
+
661
+ console.log(result.stdout);
662
+ console.log('');
663
+ console.log('Backup completed successfully!');
664
+ console.log(`Backup location: ${backupDir}`);
665
+ }
666
+ }
667
+
8
668
  /**
9
- * Creates a timestamped backup of the ~/Source directory using rsync,
10
- * excluding common development artifacts and cache directories.
669
+ * Main entry point - detects environment and executes appropriate implementation.
670
+ *
671
+ * Creates a timestamped backup of the ~/Source directory to the specified
672
+ * destination. Uses rsync on macOS/Linux (superior for file synchronization)
673
+ * and robocopy on Windows (built-in equivalent).
674
+ *
675
+ * The backup excludes common development artifacts like node_modules, .git,
676
+ * cache directories, and IDE-specific folders to reduce backup size and time.
11
677
  *
12
678
  * @param {string[]} args - Command line arguments
13
- * @param {string} args.0 - Destination backup directory path
679
+ * @param {string} args[0] - Destination backup directory path (required)
14
680
  * @returns {Promise<void>}
15
681
  */
16
- async function main(args) {
17
- // TODO: Implement Source directory backup
682
+ async function do_backup_source(args) {
683
+ const platform = os.detect();
684
+
685
+ const handlers = {
686
+ 'macos': do_backup_source_macos,
687
+ 'ubuntu': do_backup_source_ubuntu,
688
+ 'debian': do_backup_source_ubuntu,
689
+ 'raspbian': do_backup_source_raspbian,
690
+ 'amazon_linux': do_backup_source_amazon_linux,
691
+ 'rhel': do_backup_source_amazon_linux,
692
+ 'fedora': do_backup_source_amazon_linux,
693
+ 'cmd': do_backup_source_cmd,
694
+ 'windows': do_backup_source_cmd,
695
+ 'powershell': do_backup_source_powershell,
696
+ 'gitbash': do_backup_source_gitbash,
697
+ 'wsl': do_backup_source_ubuntu
698
+ };
699
+
700
+ const handler = handlers[platform.type];
701
+ if (!handler) {
702
+ console.error(`Platform '${platform.type}' is not supported for this command.`);
703
+ console.error('');
704
+ console.error('Supported platforms: macOS, Ubuntu, Debian, Raspberry Pi OS,');
705
+ console.error('Amazon Linux, RHEL, Fedora, Windows, Git Bash, WSL');
706
+ process.exit(1);
707
+ }
708
+
709
+ await handler(args);
18
710
  }
19
711
 
20
- module.exports = { main };
712
+ module.exports = {
713
+ main: do_backup_source,
714
+ do_backup_source,
715
+ do_backup_source_nodejs,
716
+ do_backup_source_macos,
717
+ do_backup_source_ubuntu,
718
+ do_backup_source_raspbian,
719
+ do_backup_source_amazon_linux,
720
+ do_backup_source_cmd,
721
+ do_backup_source_powershell,
722
+ do_backup_source_gitbash
723
+ };
21
724
 
22
725
  if (require.main === module) {
23
- main(process.argv.slice(2));
726
+ do_backup_source(process.argv.slice(2));
24
727
  }