@dahch/heimdall 1.0.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.
Files changed (3) hide show
  1. package/README.md +188 -0
  2. package/dist/cli.js +2227 -0
  3. package/package.json +61 -0
package/dist/cli.js ADDED
@@ -0,0 +1,2227 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+ import * as p from "@clack/prompts";
6
+ import pc2 from "picocolors";
7
+
8
+ // src/utils/exec.ts
9
+ import { execa } from "execa";
10
+ async function commandExists(command) {
11
+ try {
12
+ const result = await execa("which", [command], {
13
+ reject: false,
14
+ timeout: 3e3
15
+ });
16
+ return result.exitCode === 0;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+ async function safeExec(file, args = [], options = {}) {
22
+ const timeout = options.timeoutMs ?? 3e4;
23
+ try {
24
+ const subprocess = execa(file, args, {
25
+ timeout,
26
+ cwd: options.cwd,
27
+ env: {
28
+ ...process.env,
29
+ ...options.env,
30
+ // Suppress interactive prompts in subcommands
31
+ CI: "true",
32
+ DEBIAN_FRONTEND: "noninteractive"
33
+ },
34
+ reject: false,
35
+ stdin: options.stdin ?? "ignore"
36
+ });
37
+ if (options.onData && subprocess.stdout) {
38
+ subprocess.stdout.on("data", (data) => {
39
+ options.onData?.(data.toString());
40
+ });
41
+ }
42
+ if (options.onData && subprocess.stderr) {
43
+ subprocess.stderr.on("data", (data) => {
44
+ options.onData?.(data.toString());
45
+ });
46
+ }
47
+ const result = await subprocess;
48
+ return {
49
+ stdout: (result.stdout ?? "").toString(),
50
+ stderr: (result.stderr ?? "").toString(),
51
+ exitCode: result.exitCode ?? 0,
52
+ success: result.exitCode === 0,
53
+ timedOut: result.timedOut ?? false
54
+ };
55
+ } catch (err) {
56
+ const error = err instanceof Error ? err : new Error(String(err));
57
+ const isTimeout = error.name === "TimeoutError" || error.message.includes("timed out");
58
+ return {
59
+ stdout: "",
60
+ stderr: error.message,
61
+ exitCode: -1,
62
+ success: false,
63
+ timedOut: isTimeout,
64
+ error
65
+ };
66
+ }
67
+ }
68
+
69
+ // src/utils/error.ts
70
+ var ANSI_REGEX = /\u001b\[[0-9;?]*[a-zA-Z]/g;
71
+ function extractErrorMessage(rawError) {
72
+ if (!rawError) {
73
+ return "Command exited with error";
74
+ }
75
+ const stripped = rawError.replace(ANSI_REGEX, "").trim();
76
+ if (!stripped) {
77
+ return "Command exited with error";
78
+ }
79
+ const lines = stripped.split("\n").map((line) => line.trim()).filter(Boolean);
80
+ if (lines.length === 0) {
81
+ return "Command exited with error";
82
+ }
83
+ const isWarningOrNotice = (line) => {
84
+ const lower = line.toLowerCase();
85
+ return lower.startsWith("npm warn") || lower.startsWith("npm notice") || lower.startsWith("warning:") || lower.startsWith("[warn]") || lower.startsWith("warn:") || lower.startsWith("npm info");
86
+ };
87
+ const filteredLines = lines.filter((line) => !isWarningOrNotice(line));
88
+ if (filteredLines.length === 0) {
89
+ return "Command exited with error";
90
+ }
91
+ const npmErrorLines = [];
92
+ const otherErrorLines = [];
93
+ for (const line of filteredLines) {
94
+ const lower = line.toLowerCase();
95
+ if (lower.startsWith("npm error") || lower.startsWith("npm err!")) {
96
+ const cleaned = line.replace(/^npm\s+(?:error|err!)\s*:?\s*/i, "").trim();
97
+ if (cleaned) {
98
+ npmErrorLines.push(cleaned);
99
+ }
100
+ } else if (line.includes("error:") || line.includes("Error:") || line.includes("fatal:")) {
101
+ otherErrorLines.push(line);
102
+ }
103
+ }
104
+ let result = "";
105
+ if (npmErrorLines.length > 0) {
106
+ const specificLines = npmErrorLines.filter((l) => {
107
+ const low = l.toLowerCase();
108
+ return !low.startsWith("code ") && !low.startsWith("errno ") && !low.startsWith("syscall ") && !low.startsWith("path ") && !low.startsWith("a complete log of this run");
109
+ });
110
+ if (specificLines.length === 0) {
111
+ result = npmErrorLines[0];
112
+ } else if (specificLines.length === 1) {
113
+ result = specificLines[0];
114
+ } else {
115
+ const hasCommandFailed = specificLines[0].toLowerCase().startsWith("command failed");
116
+ if (hasCommandFailed && specificLines.length > 1) {
117
+ const rest = specificLines.slice(1).map((l) => l.replace(/^command\s+/i, "")).join("; ");
118
+ result = `command failed: ${rest}`;
119
+ } else {
120
+ result = specificLines.join("; ");
121
+ }
122
+ }
123
+ } else if (otherErrorLines.length > 0) {
124
+ result = otherErrorLines.join("; ");
125
+ } else {
126
+ result = filteredLines[filteredLines.length - 1];
127
+ }
128
+ result = result.replace(/[\{\[\:\,\s]+$/, "").trim();
129
+ if (!result) {
130
+ return "Command exited with error";
131
+ }
132
+ if (result.length > 120) {
133
+ return result.slice(0, 117).trim() + "...";
134
+ }
135
+ return result;
136
+ }
137
+
138
+ // src/managers/base.ts
139
+ var BasePackageManager = class {
140
+ async isAvailable() {
141
+ return commandExists(this.binary);
142
+ }
143
+ async executeStep(stepName, file, args, options, extraExecOptions) {
144
+ const startTime = Date.now();
145
+ options.onStepStart?.(stepName);
146
+ if (options.dryRun) {
147
+ options.onStepProgress?.(stepName, `[DRY-RUN] Would run: ${file} ${args.join(" ")}`);
148
+ options.onStepEnd?.(stepName, true);
149
+ return {
150
+ name: stepName,
151
+ command: `${file} ${args.join(" ")}`,
152
+ status: "success",
153
+ durationMs: 0
154
+ };
155
+ }
156
+ const result = await safeExec(file, args, {
157
+ ...extraExecOptions,
158
+ timeoutMs: options.timeoutMs ?? 18e4,
159
+ // 3 min per step
160
+ onData: (data) => {
161
+ if (options.onStepProgress) {
162
+ const lines = data.split("\n").map((l) => l.trim()).filter(Boolean);
163
+ if (lines.length > 0) {
164
+ options.onStepProgress(stepName, lines[lines.length - 1]);
165
+ }
166
+ }
167
+ }
168
+ });
169
+ const durationMs = Date.now() - startTime;
170
+ const success = result.success;
171
+ const rawOutput = [result.stderr, result.stdout].filter(Boolean).join("\n").trim();
172
+ const errorMsg = !success ? result.timedOut ? `Step timed out after ${Math.round((options.timeoutMs ?? 18e4) / 1e3)}s` : extractErrorMessage(rawOutput) : void 0;
173
+ options.onStepEnd?.(stepName, success, errorMsg, durationMs);
174
+ return {
175
+ name: stepName,
176
+ command: `${file} ${args.join(" ")}`,
177
+ status: success ? "success" : "failed",
178
+ durationMs,
179
+ error: errorMsg
180
+ };
181
+ }
182
+ };
183
+
184
+ // src/managers/homebrew.ts
185
+ var HomebrewManager = class extends BasePackageManager {
186
+ id = "homebrew";
187
+ name = "Homebrew";
188
+ icon = "\u{1F37A}";
189
+ category = "system";
190
+ binary = "brew";
191
+ async checkUpdates(options) {
192
+ const startTime = Date.now();
193
+ const isAvail = await this.isAvailable();
194
+ if (!isAvail) {
195
+ return {
196
+ managerId: this.id,
197
+ managerName: this.name,
198
+ icon: this.icon,
199
+ category: this.category,
200
+ available: false,
201
+ updates: [],
202
+ durationMs: 0
203
+ };
204
+ }
205
+ try {
206
+ const execRes = await safeExec("brew", ["outdated", "--json=v2"], {
207
+ timeoutMs: options?.timeoutMs ?? 3e4
208
+ });
209
+ if (!execRes.success) {
210
+ return {
211
+ managerId: this.id,
212
+ managerName: this.name,
213
+ icon: this.icon,
214
+ category: this.category,
215
+ available: true,
216
+ updates: [],
217
+ durationMs: Date.now() - startTime,
218
+ error: extractErrorMessage(execRes.stderr || execRes.stdout)
219
+ };
220
+ }
221
+ const updates = [];
222
+ if (execRes.stdout.trim()) {
223
+ try {
224
+ const parsed = JSON.parse(execRes.stdout);
225
+ if (Array.isArray(parsed.formulae)) {
226
+ for (const f of parsed.formulae) {
227
+ updates.push({
228
+ managerId: this.id,
229
+ managerName: this.name,
230
+ name: f.name,
231
+ currentVersion: f.installed_versions?.[0] ?? "unknown",
232
+ latestVersion: f.current_version ?? "latest",
233
+ type: "formula"
234
+ });
235
+ }
236
+ }
237
+ if (Array.isArray(parsed.casks)) {
238
+ for (const c of parsed.casks) {
239
+ const cur = Array.isArray(c.installed_versions) ? c.installed_versions[0] : c.installed_versions;
240
+ updates.push({
241
+ managerId: this.id,
242
+ managerName: this.name,
243
+ name: c.name,
244
+ currentVersion: cur ?? "unknown",
245
+ latestVersion: c.current_version ?? "latest",
246
+ type: "cask"
247
+ });
248
+ }
249
+ }
250
+ } catch {
251
+ const textRes = await safeExec("brew", ["outdated"], { timeoutMs: 15e3 });
252
+ if (textRes.stdout.trim()) {
253
+ const lines = textRes.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
254
+ for (const line of lines) {
255
+ const parts = line.split(/\s+/);
256
+ updates.push({
257
+ managerId: this.id,
258
+ managerName: this.name,
259
+ name: parts[0],
260
+ currentVersion: parts[1] ?? "unknown",
261
+ latestVersion: parts[2] ?? "latest",
262
+ type: "package"
263
+ });
264
+ }
265
+ }
266
+ }
267
+ }
268
+ return {
269
+ managerId: this.id,
270
+ managerName: this.name,
271
+ icon: this.icon,
272
+ category: this.category,
273
+ available: true,
274
+ updates,
275
+ durationMs: Date.now() - startTime
276
+ };
277
+ } catch (err) {
278
+ return {
279
+ managerId: this.id,
280
+ managerName: this.name,
281
+ icon: this.icon,
282
+ category: this.category,
283
+ available: true,
284
+ updates: [],
285
+ durationMs: Date.now() - startTime,
286
+ error: err instanceof Error ? err.message : String(err)
287
+ };
288
+ }
289
+ }
290
+ async executeUpdate(items, options) {
291
+ const startTime = Date.now();
292
+ const steps = [];
293
+ if (items.length === 0) {
294
+ return {
295
+ managerId: this.id,
296
+ managerName: this.name,
297
+ icon: this.icon,
298
+ success: true,
299
+ updatedCount: 0,
300
+ durationMs: Date.now() - startTime,
301
+ steps: []
302
+ };
303
+ }
304
+ const updateStep = await this.executeStep(
305
+ "brew update",
306
+ "brew",
307
+ ["update"],
308
+ options
309
+ );
310
+ steps.push(updateStep);
311
+ const hasFormulae = items.some((i) => i.type === "formula" || !i.type || i.type === "package");
312
+ const hasCasks = items.some((i) => i.type === "cask");
313
+ let upgradeSucceeded = false;
314
+ if (hasCasks && !hasFormulae) {
315
+ const caskStep = await this.executeStep(
316
+ "brew upgrade --cask",
317
+ "brew",
318
+ ["upgrade", "--cask"],
319
+ options
320
+ );
321
+ steps.push(caskStep);
322
+ upgradeSucceeded = caskStep.status === "success";
323
+ } else if (hasFormulae && !hasCasks) {
324
+ const formulaStep = await this.executeStep(
325
+ "brew upgrade --formula",
326
+ "brew",
327
+ ["upgrade", "--formula"],
328
+ options
329
+ );
330
+ steps.push(formulaStep);
331
+ upgradeSucceeded = formulaStep.status === "success";
332
+ } else {
333
+ const upgradeStep = await this.executeStep(
334
+ "brew upgrade",
335
+ "brew",
336
+ ["upgrade"],
337
+ options
338
+ );
339
+ steps.push(upgradeStep);
340
+ upgradeSucceeded = upgradeStep.status === "success";
341
+ }
342
+ const cleanupStep = await this.executeStep(
343
+ "brew cleanup",
344
+ "brew",
345
+ ["cleanup"],
346
+ options
347
+ );
348
+ steps.push(cleanupStep);
349
+ const failedSteps = steps.filter((s) => s.status === "failed");
350
+ const success = failedSteps.length === 0;
351
+ let error;
352
+ if (failedSteps.length > 0) {
353
+ const failedDetails = failedSteps.map((s) => `${s.name}${s.error ? `: ${s.error}` : ""}`).join("; ");
354
+ error = `${failedSteps.length} step(s) failed: ${failedDetails}`;
355
+ }
356
+ return {
357
+ managerId: this.id,
358
+ managerName: this.name,
359
+ icon: this.icon,
360
+ success,
361
+ updatedCount: upgradeSucceeded ? items.length : 0,
362
+ durationMs: Date.now() - startTime,
363
+ steps,
364
+ error
365
+ };
366
+ }
367
+ };
368
+
369
+ // src/managers/npm.ts
370
+ var NpmManager = class extends BasePackageManager {
371
+ id = "npm";
372
+ name = "npm (global)";
373
+ icon = "\u{1F4E6}";
374
+ category = "runtime";
375
+ binary = "npm";
376
+ async checkUpdates(options) {
377
+ const startTime = Date.now();
378
+ const isAvail = await this.isAvailable();
379
+ if (!isAvail) {
380
+ return {
381
+ managerId: this.id,
382
+ managerName: this.name,
383
+ icon: this.icon,
384
+ category: this.category,
385
+ available: false,
386
+ updates: [],
387
+ durationMs: 0
388
+ };
389
+ }
390
+ try {
391
+ const execRes = await safeExec("npm", ["outdated", "-g", "--json"], {
392
+ timeoutMs: options?.timeoutMs ?? 25e3
393
+ });
394
+ const updates = [];
395
+ const stdout = execRes.stdout.trim();
396
+ if (stdout.startsWith("{")) {
397
+ const parsed = JSON.parse(stdout);
398
+ for (const [name, info] of Object.entries(parsed)) {
399
+ updates.push({
400
+ managerId: this.id,
401
+ managerName: this.name,
402
+ name,
403
+ currentVersion: info.current ?? "unknown",
404
+ latestVersion: info.latest ?? info.wanted ?? "latest",
405
+ type: "global-pkg"
406
+ });
407
+ }
408
+ }
409
+ return {
410
+ managerId: this.id,
411
+ managerName: this.name,
412
+ icon: this.icon,
413
+ category: this.category,
414
+ available: true,
415
+ updates,
416
+ durationMs: Date.now() - startTime
417
+ };
418
+ } catch (err) {
419
+ return {
420
+ managerId: this.id,
421
+ managerName: this.name,
422
+ icon: this.icon,
423
+ category: this.category,
424
+ available: true,
425
+ updates: [],
426
+ durationMs: Date.now() - startTime,
427
+ error: err instanceof Error ? err.message : String(err)
428
+ };
429
+ }
430
+ }
431
+ async executeUpdate(items, options) {
432
+ const startTime = Date.now();
433
+ const steps = [];
434
+ let updatedCount = 0;
435
+ if (items.length === 0) {
436
+ return {
437
+ managerId: this.id,
438
+ managerName: this.name,
439
+ icon: this.icon,
440
+ success: true,
441
+ updatedCount: 0,
442
+ durationMs: Date.now() - startTime,
443
+ steps: []
444
+ };
445
+ }
446
+ let runIsolatedFallback = true;
447
+ if (items.length > 1) {
448
+ const targets = items.map(
449
+ (i) => i.latestVersion && i.latestVersion !== "unknown" ? `${i.name}@${i.latestVersion}` : `${i.name}@latest`
450
+ );
451
+ const batchStep = await this.executeStep(
452
+ `npm install -g --legacy-peer-deps (${items.length} pkgs)`,
453
+ "npm",
454
+ ["install", "-g", "--legacy-peer-deps", "--", ...targets],
455
+ options
456
+ );
457
+ if (batchStep.status === "success") {
458
+ steps.push(batchStep);
459
+ updatedCount = items.length;
460
+ runIsolatedFallback = false;
461
+ } else {
462
+ batchStep.status = "skipped";
463
+ steps.push(batchStep);
464
+ options.onStepProgress?.(
465
+ "npm install",
466
+ "Batch install hit conflicts; falling back to per-package isolated installs..."
467
+ );
468
+ }
469
+ }
470
+ if (runIsolatedFallback) {
471
+ for (const item of items) {
472
+ const pkgTarget = item.latestVersion && item.latestVersion !== "unknown" ? `${item.name}@${item.latestVersion}` : `${item.name}@latest`;
473
+ const itemStep = await this.executeStep(
474
+ `npm install -g ${item.name}`,
475
+ "npm",
476
+ ["install", "-g", "--legacy-peer-deps", "--", pkgTarget],
477
+ options
478
+ );
479
+ if (itemStep.status === "success") {
480
+ updatedCount++;
481
+ steps.push(itemStep);
482
+ } else {
483
+ const errLower = (itemStep.error ?? "").toLowerCase();
484
+ const isScriptIssue = errLower.includes("only-allow") || errLower.includes("sh -c") || errLower.includes("preinstall") || errLower.includes("postinstall") || errLower.includes("install script") || errLower.includes("node-gyp") || errLower.includes("254");
485
+ if (isScriptIssue) {
486
+ options.onStepProgress?.(
487
+ `npm install -g ${item.name}`,
488
+ `Install script failed; retrying with --ignore-scripts...`
489
+ );
490
+ const retryStep = await this.executeStep(
491
+ `npm install -g ${item.name} (--ignore-scripts)`,
492
+ "npm",
493
+ ["install", "-g", "--legacy-peer-deps", "--ignore-scripts", "--", pkgTarget],
494
+ options
495
+ );
496
+ if (retryStep.status === "success") {
497
+ updatedCount++;
498
+ steps.push(retryStep);
499
+ } else {
500
+ steps.push(retryStep);
501
+ }
502
+ } else {
503
+ steps.push(itemStep);
504
+ }
505
+ }
506
+ }
507
+ }
508
+ const failedSteps = steps.filter((s) => s.status === "failed");
509
+ const success = failedSteps.length === 0;
510
+ let errorSummary;
511
+ if (!success) {
512
+ const failedDetails = failedSteps.map((s) => `${s.name}${s.error ? `: ${s.error}` : ""}`).join("; ");
513
+ errorSummary = `${failedSteps.length} step(s) failed: ${failedDetails}`;
514
+ }
515
+ return {
516
+ managerId: this.id,
517
+ managerName: this.name,
518
+ icon: this.icon,
519
+ success,
520
+ updatedCount,
521
+ durationMs: Date.now() - startTime,
522
+ steps,
523
+ error: errorSummary
524
+ };
525
+ }
526
+ };
527
+
528
+ // src/managers/pnpm.ts
529
+ var PnpmManager = class extends BasePackageManager {
530
+ id = "pnpm";
531
+ name = "pnpm (global)";
532
+ icon = "\u26A1";
533
+ category = "runtime";
534
+ binary = "pnpm";
535
+ async checkUpdates(options) {
536
+ const startTime = Date.now();
537
+ const isAvail = await this.isAvailable();
538
+ if (!isAvail) {
539
+ return {
540
+ managerId: this.id,
541
+ managerName: this.name,
542
+ icon: this.icon,
543
+ category: this.category,
544
+ available: false,
545
+ updates: [],
546
+ durationMs: 0
547
+ };
548
+ }
549
+ try {
550
+ const execRes = await safeExec("pnpm", ["outdated", "-g", "--format", "json"], {
551
+ timeoutMs: options?.timeoutMs ?? 25e3
552
+ });
553
+ const updates = [];
554
+ const stdout = execRes.stdout.trim();
555
+ const jsonStart = stdout.indexOf("{");
556
+ const jsonEnd = stdout.lastIndexOf("}");
557
+ if (jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart) {
558
+ const jsonStr = stdout.slice(jsonStart, jsonEnd + 1);
559
+ try {
560
+ const parsed = JSON.parse(jsonStr);
561
+ for (const [name, info] of Object.entries(parsed)) {
562
+ updates.push({
563
+ managerId: this.id,
564
+ managerName: this.name,
565
+ name,
566
+ currentVersion: info.current ?? "unknown",
567
+ latestVersion: info.latest ?? info.wanted ?? "latest",
568
+ type: "global-pkg"
569
+ });
570
+ }
571
+ } catch {
572
+ }
573
+ }
574
+ return {
575
+ managerId: this.id,
576
+ managerName: this.name,
577
+ icon: this.icon,
578
+ category: this.category,
579
+ available: true,
580
+ updates,
581
+ durationMs: Date.now() - startTime
582
+ };
583
+ } catch (err) {
584
+ return {
585
+ managerId: this.id,
586
+ managerName: this.name,
587
+ icon: this.icon,
588
+ category: this.category,
589
+ available: true,
590
+ updates: [],
591
+ durationMs: Date.now() - startTime,
592
+ error: err instanceof Error ? err.message : String(err)
593
+ };
594
+ }
595
+ }
596
+ async executeUpdate(items, options) {
597
+ const startTime = Date.now();
598
+ const steps = [];
599
+ const updateStep = await this.executeStep(
600
+ "pnpm update -g --latest",
601
+ "pnpm",
602
+ ["update", "-g", "--latest"],
603
+ options
604
+ );
605
+ steps.push(updateStep);
606
+ const success = steps.every((s) => s.status === "success");
607
+ return {
608
+ managerId: this.id,
609
+ managerName: this.name,
610
+ icon: this.icon,
611
+ success,
612
+ updatedCount: success ? items.length : 0,
613
+ durationMs: Date.now() - startTime,
614
+ steps,
615
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
616
+ };
617
+ }
618
+ };
619
+
620
+ // src/managers/bun.ts
621
+ var BunManager = class extends BasePackageManager {
622
+ id = "bun";
623
+ name = "Bun";
624
+ icon = "\u{1F35E}";
625
+ category = "runtime";
626
+ binary = "bun";
627
+ async checkUpdates(options) {
628
+ const startTime = Date.now();
629
+ const isAvail = await this.isAvailable();
630
+ if (!isAvail) {
631
+ return {
632
+ managerId: this.id,
633
+ managerName: this.name,
634
+ icon: this.icon,
635
+ category: this.category,
636
+ available: false,
637
+ updates: [],
638
+ durationMs: 0
639
+ };
640
+ }
641
+ try {
642
+ const execRes = await safeExec("bun", ["outdated", "-g"], {
643
+ timeoutMs: options?.timeoutMs ?? 25e3
644
+ });
645
+ const updates = [];
646
+ const lines = execRes.stdout.split("\n");
647
+ for (const line of lines) {
648
+ const trimmed = line.trim();
649
+ if (!trimmed.startsWith("|") || trimmed.includes("---") || trimmed.includes("Package")) {
650
+ continue;
651
+ }
652
+ const cols = trimmed.split("|").map((c) => c.trim()).filter(Boolean);
653
+ if (cols.length >= 3) {
654
+ const pkgName = cols[0];
655
+ const current = cols[1];
656
+ const latest = cols[cols.length - 1];
657
+ if (pkgName && current && latest) {
658
+ updates.push({
659
+ managerId: this.id,
660
+ managerName: this.name,
661
+ name: pkgName,
662
+ currentVersion: current,
663
+ latestVersion: latest,
664
+ type: "global-pkg"
665
+ });
666
+ }
667
+ }
668
+ }
669
+ return {
670
+ managerId: this.id,
671
+ managerName: this.name,
672
+ icon: this.icon,
673
+ category: this.category,
674
+ available: true,
675
+ updates,
676
+ durationMs: Date.now() - startTime
677
+ };
678
+ } catch (err) {
679
+ return {
680
+ managerId: this.id,
681
+ managerName: this.name,
682
+ icon: this.icon,
683
+ category: this.category,
684
+ available: true,
685
+ updates: [],
686
+ durationMs: Date.now() - startTime,
687
+ error: err instanceof Error ? err.message : String(err)
688
+ };
689
+ }
690
+ }
691
+ async executeUpdate(items, options) {
692
+ const startTime = Date.now();
693
+ const steps = [];
694
+ if (items.length > 0) {
695
+ const pkgsToUpgrade = items.map(
696
+ (item) => item.latestVersion && item.latestVersion !== "latest" && item.latestVersion !== "unknown" ? `${item.name}@${item.latestVersion}` : `${item.name}@latest`
697
+ );
698
+ const addStep = await this.executeStep(
699
+ `bun add -g ${pkgsToUpgrade.length} package(s)`,
700
+ "bun",
701
+ ["add", "-g", ...pkgsToUpgrade],
702
+ options
703
+ );
704
+ steps.push(addStep);
705
+ }
706
+ const updateStep = await this.executeStep(
707
+ "bun update -g",
708
+ "bun",
709
+ ["update", "-g"],
710
+ options
711
+ );
712
+ steps.push(updateStep);
713
+ const whichBun = await safeExec("which", ["bun"], { timeoutMs: 3e3 });
714
+ const isBrewBun = whichBun.stdout.includes("/opt/homebrew") || whichBun.stdout.includes("/Cellar");
715
+ if (!isBrewBun) {
716
+ const upgradeStep = await this.executeStep(
717
+ "bun upgrade",
718
+ "bun",
719
+ ["upgrade"],
720
+ options
721
+ );
722
+ if (upgradeStep.status === "failed" && upgradeStep.error?.includes("Homebrew")) {
723
+ upgradeStep.status = "skipped";
724
+ }
725
+ steps.push(upgradeStep);
726
+ }
727
+ const hasFailedStep = steps.some((s) => s.status === "failed");
728
+ const success = !hasFailedStep;
729
+ return {
730
+ managerId: this.id,
731
+ managerName: this.name,
732
+ icon: this.icon,
733
+ success,
734
+ updatedCount: success ? items.length : 0,
735
+ durationMs: Date.now() - startTime,
736
+ steps,
737
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
738
+ };
739
+ }
740
+ };
741
+
742
+ // src/managers/yarn.ts
743
+ var YarnManager = class extends BasePackageManager {
744
+ id = "yarn";
745
+ name = "Yarn (global)";
746
+ icon = "\u{1F9F6}";
747
+ category = "runtime";
748
+ binary = "yarn";
749
+ async checkUpdates(options) {
750
+ const startTime = Date.now();
751
+ const isAvail = await this.isAvailable();
752
+ if (!isAvail) {
753
+ return {
754
+ managerId: this.id,
755
+ managerName: this.name,
756
+ icon: this.icon,
757
+ category: this.category,
758
+ available: false,
759
+ updates: [],
760
+ durationMs: 0
761
+ };
762
+ }
763
+ try {
764
+ const execRes = await safeExec("yarn", ["global", "list", "--depth=0"], {
765
+ timeoutMs: options?.timeoutMs ?? 15e3
766
+ });
767
+ const updates = [];
768
+ const lines = execRes.stdout.split("\n");
769
+ const installed = [];
770
+ for (const line of lines) {
771
+ const match = line.match(/info\s+"((?:@[^/]+\/)?[^@]+)@([^"]+)"/);
772
+ if (match) {
773
+ installed.push({ name: match[1], currentVersion: match[2] });
774
+ }
775
+ }
776
+ await Promise.all(
777
+ installed.map(async ({ name, currentVersion }) => {
778
+ try {
779
+ const res = await fetch(
780
+ `https://registry.npmjs.org/${encodeURIComponent(name).replace("%40", "@")}/latest`,
781
+ {
782
+ signal: AbortSignal.timeout(3e3),
783
+ headers: { Accept: "application/json" }
784
+ }
785
+ );
786
+ if (res.ok) {
787
+ const data = await res.json();
788
+ const latest = data.version;
789
+ if (latest && latest !== currentVersion) {
790
+ updates.push({
791
+ managerId: this.id,
792
+ managerName: this.name,
793
+ name,
794
+ currentVersion,
795
+ latestVersion: latest,
796
+ type: "global-pkg"
797
+ });
798
+ }
799
+ }
800
+ } catch {
801
+ }
802
+ })
803
+ );
804
+ return {
805
+ managerId: this.id,
806
+ managerName: this.name,
807
+ icon: this.icon,
808
+ category: this.category,
809
+ available: true,
810
+ updates,
811
+ durationMs: Date.now() - startTime
812
+ };
813
+ } catch (err) {
814
+ return {
815
+ managerId: this.id,
816
+ managerName: this.name,
817
+ icon: this.icon,
818
+ category: this.category,
819
+ available: true,
820
+ updates: [],
821
+ durationMs: Date.now() - startTime,
822
+ error: err instanceof Error ? err.message : String(err)
823
+ };
824
+ }
825
+ }
826
+ async executeUpdate(items, options) {
827
+ const startTime = Date.now();
828
+ const steps = [];
829
+ if (items.length > 0) {
830
+ const pkgsToUpgrade = items.map(
831
+ (item) => item.latestVersion && item.latestVersion !== "latest" && item.latestVersion !== "unknown" ? `${item.name}@${item.latestVersion}` : `${item.name}@latest`
832
+ );
833
+ const addStep = await this.executeStep(
834
+ `yarn global add ${pkgsToUpgrade.length} package(s)`,
835
+ "yarn",
836
+ ["global", "add", ...pkgsToUpgrade],
837
+ options
838
+ );
839
+ steps.push(addStep);
840
+ }
841
+ const upgradeStep = await this.executeStep(
842
+ "yarn global upgrade",
843
+ "yarn",
844
+ ["global", "upgrade"],
845
+ options
846
+ );
847
+ steps.push(upgradeStep);
848
+ const success = steps.every((s) => s.status === "success");
849
+ return {
850
+ managerId: this.id,
851
+ managerName: this.name,
852
+ icon: this.icon,
853
+ success,
854
+ updatedCount: success ? items.length : 0,
855
+ durationMs: Date.now() - startTime,
856
+ steps,
857
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
858
+ };
859
+ }
860
+ };
861
+
862
+ // src/managers/python.ts
863
+ var PythonPipManager = class extends BasePackageManager {
864
+ id = "pip";
865
+ name = "Python (pip user)";
866
+ icon = "\u{1F40D}";
867
+ category = "language";
868
+ binary = "pip3";
869
+ activeBinary = "pip3";
870
+ async isAvailable() {
871
+ if (await commandExists("pip3")) {
872
+ this.activeBinary = "pip3";
873
+ return true;
874
+ }
875
+ if (await commandExists("pip")) {
876
+ this.activeBinary = "pip";
877
+ return true;
878
+ }
879
+ return false;
880
+ }
881
+ async checkUpdates(options) {
882
+ const startTime = Date.now();
883
+ const isAvail = await this.isAvailable();
884
+ if (!isAvail) {
885
+ return {
886
+ managerId: this.id,
887
+ managerName: this.name,
888
+ icon: this.icon,
889
+ category: this.category,
890
+ available: false,
891
+ updates: [],
892
+ durationMs: 0
893
+ };
894
+ }
895
+ try {
896
+ const execRes = await safeExec(
897
+ this.activeBinary,
898
+ ["list", "--user", "--outdated", "--format=json"],
899
+ { timeoutMs: options?.timeoutMs ?? 15e3 }
900
+ );
901
+ const updates = [];
902
+ const stdout = execRes.stdout.trim();
903
+ if (stdout.startsWith("[")) {
904
+ try {
905
+ const parsed = JSON.parse(stdout);
906
+ for (const item of parsed) {
907
+ updates.push({
908
+ managerId: this.id,
909
+ managerName: this.name,
910
+ name: item.name,
911
+ currentVersion: item.version ?? "unknown",
912
+ latestVersion: item.latest_version ?? "latest",
913
+ type: "pip-user"
914
+ });
915
+ }
916
+ } catch {
917
+ }
918
+ }
919
+ return {
920
+ managerId: this.id,
921
+ managerName: this.name,
922
+ icon: this.icon,
923
+ category: this.category,
924
+ available: true,
925
+ updates,
926
+ durationMs: Date.now() - startTime
927
+ };
928
+ } catch (err) {
929
+ return {
930
+ managerId: this.id,
931
+ managerName: this.name,
932
+ icon: this.icon,
933
+ category: this.category,
934
+ available: true,
935
+ updates: [],
936
+ durationMs: Date.now() - startTime,
937
+ error: err instanceof Error ? err.message : String(err)
938
+ };
939
+ }
940
+ }
941
+ async executeUpdate(items, options) {
942
+ const startTime = Date.now();
943
+ const steps = [];
944
+ if (items.length > 0) {
945
+ const packageNames = items.map((i) => i.name);
946
+ const step = await this.executeStep(
947
+ `${this.activeBinary} install --user --upgrade (${items.length} pkgs)`,
948
+ this.activeBinary,
949
+ ["install", "--user", "--upgrade", ...packageNames],
950
+ options
951
+ );
952
+ steps.push(step);
953
+ }
954
+ const success = steps.every((s) => s.status === "success");
955
+ return {
956
+ managerId: this.id,
957
+ managerName: this.name,
958
+ icon: this.icon,
959
+ success,
960
+ updatedCount: success ? items.length : 0,
961
+ durationMs: Date.now() - startTime,
962
+ steps,
963
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
964
+ };
965
+ }
966
+ };
967
+
968
+ // src/managers/pipx.ts
969
+ var PipxManager = class extends BasePackageManager {
970
+ id = "pipx";
971
+ name = "pipx (Python CLI tools)";
972
+ icon = "\u{1F4E6}";
973
+ category = "language";
974
+ binary = "pipx";
975
+ async checkUpdates(options) {
976
+ const startTime = Date.now();
977
+ const isAvail = await this.isAvailable();
978
+ if (!isAvail) {
979
+ return {
980
+ managerId: this.id,
981
+ managerName: this.name,
982
+ icon: this.icon,
983
+ category: this.category,
984
+ available: false,
985
+ updates: [],
986
+ durationMs: 0
987
+ };
988
+ }
989
+ try {
990
+ const execRes = await safeExec("pipx", ["list", "--json"], {
991
+ timeoutMs: options?.timeoutMs ?? 15e3
992
+ });
993
+ const updates = [];
994
+ const stdout = execRes.stdout.trim();
995
+ if (stdout.startsWith("{")) {
996
+ try {
997
+ const parsed = JSON.parse(stdout);
998
+ if (parsed.venvs) {
999
+ const entries = Object.entries(parsed.venvs);
1000
+ await Promise.all(
1001
+ entries.map(async ([pkgName, venvData]) => {
1002
+ const current = venvData.metadata?.main_package?.package_version;
1003
+ if (!current) return;
1004
+ try {
1005
+ const response = await fetch(
1006
+ `https://pypi.org/pypi/${encodeURIComponent(pkgName)}/json`,
1007
+ { signal: AbortSignal.timeout(3e3) }
1008
+ );
1009
+ if (response.ok) {
1010
+ const data = await response.json();
1011
+ const latest = data.info?.version;
1012
+ if (latest && latest !== current) {
1013
+ updates.push({
1014
+ managerId: this.id,
1015
+ managerName: this.name,
1016
+ name: pkgName,
1017
+ currentVersion: current,
1018
+ latestVersion: latest,
1019
+ type: "pipx-app"
1020
+ });
1021
+ }
1022
+ }
1023
+ } catch {
1024
+ }
1025
+ })
1026
+ );
1027
+ }
1028
+ } catch {
1029
+ }
1030
+ }
1031
+ return {
1032
+ managerId: this.id,
1033
+ managerName: this.name,
1034
+ icon: this.icon,
1035
+ category: this.category,
1036
+ available: true,
1037
+ updates,
1038
+ durationMs: Date.now() - startTime
1039
+ };
1040
+ } catch (err) {
1041
+ return {
1042
+ managerId: this.id,
1043
+ managerName: this.name,
1044
+ icon: this.icon,
1045
+ category: this.category,
1046
+ available: true,
1047
+ updates: [],
1048
+ durationMs: Date.now() - startTime,
1049
+ error: err instanceof Error ? err.message : String(err)
1050
+ };
1051
+ }
1052
+ }
1053
+ async executeUpdate(items, options) {
1054
+ const startTime = Date.now();
1055
+ const steps = [];
1056
+ const upgradeStep = await this.executeStep(
1057
+ "pipx upgrade-all",
1058
+ "pipx",
1059
+ ["upgrade-all"],
1060
+ options
1061
+ );
1062
+ steps.push(upgradeStep);
1063
+ const success = steps.every((s) => s.status === "success");
1064
+ return {
1065
+ managerId: this.id,
1066
+ managerName: this.name,
1067
+ icon: this.icon,
1068
+ success,
1069
+ updatedCount: success ? items.length : 0,
1070
+ durationMs: Date.now() - startTime,
1071
+ steps,
1072
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
1073
+ };
1074
+ }
1075
+ };
1076
+
1077
+ // src/managers/cargo.ts
1078
+ var CargoManager = class extends BasePackageManager {
1079
+ id = "cargo";
1080
+ name = "Rust (Cargo & Rustup)";
1081
+ icon = "\u{1F980}";
1082
+ category = "language";
1083
+ binary = "cargo";
1084
+ async isAvailable() {
1085
+ return await commandExists("cargo") || await commandExists("rustup");
1086
+ }
1087
+ async checkUpdates(options) {
1088
+ const startTime = Date.now();
1089
+ const isAvail = await this.isAvailable();
1090
+ if (!isAvail) {
1091
+ return {
1092
+ managerId: this.id,
1093
+ managerName: this.name,
1094
+ icon: this.icon,
1095
+ category: this.category,
1096
+ available: false,
1097
+ updates: [],
1098
+ durationMs: 0
1099
+ };
1100
+ }
1101
+ try {
1102
+ const updates = [];
1103
+ if (await commandExists("rustup")) {
1104
+ const checkRes = await safeExec("rustup", ["check"], {
1105
+ timeoutMs: options?.timeoutMs ?? 15e3
1106
+ });
1107
+ const lines = checkRes.stdout.split("\n");
1108
+ for (const line of lines) {
1109
+ if (line.includes("Update available")) {
1110
+ const parts = line.trim().split(/\s+/);
1111
+ const toolchain = parts[0] ?? "toolchain";
1112
+ updates.push({
1113
+ managerId: this.id,
1114
+ managerName: this.name,
1115
+ name: `rustup (${toolchain})`,
1116
+ currentVersion: "installed",
1117
+ latestVersion: "update-available",
1118
+ type: "toolchain"
1119
+ });
1120
+ }
1121
+ }
1122
+ }
1123
+ if (await commandExists("cargo-install-update")) {
1124
+ const listRes = await safeExec("cargo", ["install-update", "-l"], {
1125
+ timeoutMs: options?.timeoutMs ?? 15e3
1126
+ });
1127
+ const lines = listRes.stdout.split("\n");
1128
+ for (const line of lines) {
1129
+ const match = line.match(/^(\S+)\s+v(\S+)\s+v(\S+)/);
1130
+ if (match) {
1131
+ const [, name, cur, latest] = match;
1132
+ if (cur !== latest) {
1133
+ updates.push({
1134
+ managerId: this.id,
1135
+ managerName: this.name,
1136
+ name,
1137
+ currentVersion: cur,
1138
+ latestVersion: latest,
1139
+ type: "crate"
1140
+ });
1141
+ }
1142
+ }
1143
+ }
1144
+ }
1145
+ return {
1146
+ managerId: this.id,
1147
+ managerName: this.name,
1148
+ icon: this.icon,
1149
+ category: this.category,
1150
+ available: true,
1151
+ updates,
1152
+ durationMs: Date.now() - startTime
1153
+ };
1154
+ } catch (err) {
1155
+ return {
1156
+ managerId: this.id,
1157
+ managerName: this.name,
1158
+ icon: this.icon,
1159
+ category: this.category,
1160
+ available: true,
1161
+ updates: [],
1162
+ durationMs: Date.now() - startTime,
1163
+ error: err instanceof Error ? err.message : String(err)
1164
+ };
1165
+ }
1166
+ }
1167
+ async executeUpdate(items, options) {
1168
+ const startTime = Date.now();
1169
+ const steps = [];
1170
+ if (await commandExists("rustup")) {
1171
+ const rustupStep = await this.executeStep(
1172
+ "rustup update",
1173
+ "rustup",
1174
+ ["update"],
1175
+ options
1176
+ );
1177
+ steps.push(rustupStep);
1178
+ }
1179
+ if (await commandExists("cargo-install-update")) {
1180
+ const cargoStep = await this.executeStep(
1181
+ "cargo install-update -a",
1182
+ "cargo",
1183
+ ["install-update", "-a"],
1184
+ options
1185
+ );
1186
+ steps.push(cargoStep);
1187
+ }
1188
+ const hasFailedStep = steps.some((s) => s.status === "failed");
1189
+ const success = !hasFailedStep;
1190
+ return {
1191
+ managerId: this.id,
1192
+ managerName: this.name,
1193
+ icon: this.icon,
1194
+ success,
1195
+ updatedCount: success ? items.length : 0,
1196
+ durationMs: Date.now() - startTime,
1197
+ steps,
1198
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
1199
+ };
1200
+ }
1201
+ };
1202
+
1203
+ // src/managers/gem.ts
1204
+ var RubyGemManager = class extends BasePackageManager {
1205
+ id = "gem";
1206
+ name = "Ruby Gem";
1207
+ icon = "\u{1F48E}";
1208
+ category = "language";
1209
+ binary = "gem";
1210
+ async isAvailable() {
1211
+ const hasCmd = await commandExists(this.binary);
1212
+ if (!hasCmd) return false;
1213
+ const whichRes = await safeExec("which", ["gem"], { timeoutMs: 3e3 });
1214
+ const gemPath = whichRes.stdout.trim();
1215
+ if (gemPath === "/usr/bin/gem" || gemPath.startsWith("/System/Library")) {
1216
+ return false;
1217
+ }
1218
+ return true;
1219
+ }
1220
+ async checkUpdates(options) {
1221
+ const startTime = Date.now();
1222
+ const isAvail = await this.isAvailable();
1223
+ if (!isAvail) {
1224
+ return {
1225
+ managerId: this.id,
1226
+ managerName: this.name,
1227
+ icon: this.icon,
1228
+ category: this.category,
1229
+ available: false,
1230
+ updates: [],
1231
+ durationMs: 0
1232
+ };
1233
+ }
1234
+ try {
1235
+ const execRes = await safeExec("gem", ["outdated"], {
1236
+ timeoutMs: options?.timeoutMs ?? 2e4
1237
+ });
1238
+ const updates = [];
1239
+ const lines = execRes.stdout.split("\n");
1240
+ for (const line of lines) {
1241
+ const trimmed = line.trim();
1242
+ const match = trimmed.match(/^(\S+)\s*\(([^<]+)<\s*([^)]+)\)/);
1243
+ if (match) {
1244
+ const [, name, current, latest] = match;
1245
+ updates.push({
1246
+ managerId: this.id,
1247
+ managerName: this.name,
1248
+ name: name.trim(),
1249
+ currentVersion: current.trim(),
1250
+ latestVersion: latest.trim(),
1251
+ type: "gem"
1252
+ });
1253
+ }
1254
+ }
1255
+ return {
1256
+ managerId: this.id,
1257
+ managerName: this.name,
1258
+ icon: this.icon,
1259
+ category: this.category,
1260
+ available: true,
1261
+ updates,
1262
+ durationMs: Date.now() - startTime
1263
+ };
1264
+ } catch (err) {
1265
+ return {
1266
+ managerId: this.id,
1267
+ managerName: this.name,
1268
+ icon: this.icon,
1269
+ category: this.category,
1270
+ available: true,
1271
+ updates: [],
1272
+ durationMs: Date.now() - startTime,
1273
+ error: err instanceof Error ? err.message : String(err)
1274
+ };
1275
+ }
1276
+ }
1277
+ async executeUpdate(items, options) {
1278
+ const startTime = Date.now();
1279
+ const steps = [];
1280
+ const updateStep = await this.executeStep(
1281
+ "gem update --user-install",
1282
+ "gem",
1283
+ ["update", "--user-install"],
1284
+ options
1285
+ );
1286
+ steps.push(updateStep);
1287
+ const cleanupStep = await this.executeStep(
1288
+ "gem cleanup",
1289
+ "gem",
1290
+ ["cleanup"],
1291
+ options
1292
+ );
1293
+ steps.push(cleanupStep);
1294
+ const success = steps.every((s) => s.status === "success");
1295
+ return {
1296
+ managerId: this.id,
1297
+ managerName: this.name,
1298
+ icon: this.icon,
1299
+ success,
1300
+ updatedCount: success ? items.length : 0,
1301
+ durationMs: Date.now() - startTime,
1302
+ steps,
1303
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
1304
+ };
1305
+ }
1306
+ };
1307
+
1308
+ // src/managers/macports.ts
1309
+ var MacPortsManager = class extends BasePackageManager {
1310
+ id = "macports";
1311
+ name = "MacPorts";
1312
+ icon = "\u2693";
1313
+ category = "system";
1314
+ binary = "port";
1315
+ async checkUpdates(options) {
1316
+ const startTime = Date.now();
1317
+ const isAvail = await this.isAvailable();
1318
+ if (!isAvail) {
1319
+ return {
1320
+ managerId: this.id,
1321
+ managerName: this.name,
1322
+ icon: this.icon,
1323
+ category: this.category,
1324
+ available: false,
1325
+ updates: [],
1326
+ durationMs: 0
1327
+ };
1328
+ }
1329
+ try {
1330
+ const execRes = await safeExec("port", ["outdated"], {
1331
+ timeoutMs: options?.timeoutMs ?? 2e4
1332
+ });
1333
+ const updates = [];
1334
+ const lines = execRes.stdout.split("\n");
1335
+ for (const line of lines) {
1336
+ const trimmed = line.trim();
1337
+ if (!trimmed || trimmed.startsWith("The following") || trimmed.startsWith("--")) {
1338
+ continue;
1339
+ }
1340
+ const match = trimmed.match(/^(\S+)\s+(\S+)\s*<\s*(\S+)/);
1341
+ if (match) {
1342
+ const [, name, current, latest] = match;
1343
+ updates.push({
1344
+ managerId: this.id,
1345
+ managerName: this.name,
1346
+ name,
1347
+ currentVersion: current,
1348
+ latestVersion: latest,
1349
+ type: "port"
1350
+ });
1351
+ }
1352
+ }
1353
+ return {
1354
+ managerId: this.id,
1355
+ managerName: this.name,
1356
+ icon: this.icon,
1357
+ category: this.category,
1358
+ available: true,
1359
+ updates,
1360
+ durationMs: Date.now() - startTime
1361
+ };
1362
+ } catch (err) {
1363
+ return {
1364
+ managerId: this.id,
1365
+ managerName: this.name,
1366
+ icon: this.icon,
1367
+ category: this.category,
1368
+ available: true,
1369
+ updates: [],
1370
+ durationMs: Date.now() - startTime,
1371
+ error: err instanceof Error ? err.message : String(err)
1372
+ };
1373
+ }
1374
+ }
1375
+ async executeUpdate(items, options) {
1376
+ const startTime = Date.now();
1377
+ const steps = [];
1378
+ if (!options.dryRun && process.getuid?.() !== 0) {
1379
+ const sudoCheck = await safeExec("sudo", ["-n", "true"], { timeoutMs: 3e3 });
1380
+ if (!sudoCheck.success) {
1381
+ return {
1382
+ managerId: this.id,
1383
+ managerName: this.name,
1384
+ icon: this.icon,
1385
+ success: false,
1386
+ updatedCount: 0,
1387
+ durationMs: Date.now() - startTime,
1388
+ steps: [
1389
+ {
1390
+ name: "sudo privileges",
1391
+ command: "sudo -n true",
1392
+ status: "failed",
1393
+ error: 'MacPorts requires sudo privileges. Run "sudo -v" in your terminal before hmd.'
1394
+ }
1395
+ ],
1396
+ error: 'MacPorts requires sudo privileges. Run "sudo -v" before running hmd.'
1397
+ };
1398
+ }
1399
+ }
1400
+ const selfupdateStep = await this.executeStep(
1401
+ "sudo port selfupdate",
1402
+ "sudo",
1403
+ ["port", "selfupdate"],
1404
+ options
1405
+ );
1406
+ steps.push(selfupdateStep);
1407
+ const upgradeStep = await this.executeStep(
1408
+ "sudo port upgrade outdated",
1409
+ "sudo",
1410
+ ["port", "upgrade", "outdated"],
1411
+ options
1412
+ );
1413
+ steps.push(upgradeStep);
1414
+ const success = steps.every((s) => s.status === "success");
1415
+ return {
1416
+ managerId: this.id,
1417
+ managerName: this.name,
1418
+ icon: this.icon,
1419
+ success,
1420
+ updatedCount: success ? items.length : 0,
1421
+ durationMs: Date.now() - startTime,
1422
+ steps,
1423
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
1424
+ };
1425
+ }
1426
+ };
1427
+
1428
+ // src/managers/mas.ts
1429
+ var MacAppStoreManager = class extends BasePackageManager {
1430
+ id = "mas";
1431
+ name = "Mac App Store";
1432
+ icon = "\u{1F34E}";
1433
+ category = "appstore";
1434
+ binary = "mas";
1435
+ async checkUpdates(options) {
1436
+ const startTime = Date.now();
1437
+ const isAvail = await this.isAvailable();
1438
+ if (!isAvail) {
1439
+ return {
1440
+ managerId: this.id,
1441
+ managerName: this.name,
1442
+ icon: this.icon,
1443
+ category: this.category,
1444
+ available: false,
1445
+ updates: [],
1446
+ durationMs: 0
1447
+ };
1448
+ }
1449
+ try {
1450
+ const execRes = await safeExec("mas", ["outdated"], {
1451
+ timeoutMs: options?.timeoutMs ?? 2e4
1452
+ });
1453
+ const updates = [];
1454
+ const lines = execRes.stdout.split("\n");
1455
+ for (const line of lines) {
1456
+ const trimmed = line.trim();
1457
+ const match = trimmed.match(/^\s*(\d+)\s+(.+?)\s*\(([^->]+)->\s*([^)]+)\)/);
1458
+ if (match) {
1459
+ const [, id, appName, current, latest] = match;
1460
+ updates.push({
1461
+ managerId: this.id,
1462
+ managerName: this.name,
1463
+ name: `${appName.trim()} [${id}]`,
1464
+ currentVersion: current.trim(),
1465
+ latestVersion: latest.trim(),
1466
+ type: "mas-app"
1467
+ });
1468
+ }
1469
+ }
1470
+ return {
1471
+ managerId: this.id,
1472
+ managerName: this.name,
1473
+ icon: this.icon,
1474
+ category: this.category,
1475
+ available: true,
1476
+ updates,
1477
+ durationMs: Date.now() - startTime
1478
+ };
1479
+ } catch (err) {
1480
+ return {
1481
+ managerId: this.id,
1482
+ managerName: this.name,
1483
+ icon: this.icon,
1484
+ category: this.category,
1485
+ available: true,
1486
+ updates: [],
1487
+ durationMs: Date.now() - startTime,
1488
+ error: err instanceof Error ? err.message : String(err)
1489
+ };
1490
+ }
1491
+ }
1492
+ async executeUpdate(items, options) {
1493
+ const startTime = Date.now();
1494
+ const steps = [];
1495
+ const upgradeStep = await this.executeStep(
1496
+ "mas upgrade",
1497
+ "mas",
1498
+ ["upgrade"],
1499
+ options
1500
+ );
1501
+ steps.push(upgradeStep);
1502
+ const success = steps.every((s) => s.status === "success");
1503
+ return {
1504
+ managerId: this.id,
1505
+ managerName: this.name,
1506
+ icon: this.icon,
1507
+ success,
1508
+ updatedCount: success ? items.length : 0,
1509
+ durationMs: Date.now() - startTime,
1510
+ steps,
1511
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
1512
+ };
1513
+ }
1514
+ };
1515
+
1516
+ // src/managers/linux.ts
1517
+ var AptManager = class extends BasePackageManager {
1518
+ id = "apt";
1519
+ name = "APT (Debian/Ubuntu)";
1520
+ icon = "\u{1F427}";
1521
+ category = "system";
1522
+ binary = "apt-get";
1523
+ async checkUpdates(options) {
1524
+ const startTime = Date.now();
1525
+ const isAvail = await this.isAvailable();
1526
+ if (!isAvail) {
1527
+ return {
1528
+ managerId: this.id,
1529
+ managerName: this.name,
1530
+ icon: this.icon,
1531
+ category: this.category,
1532
+ available: false,
1533
+ updates: [],
1534
+ durationMs: 0
1535
+ };
1536
+ }
1537
+ try {
1538
+ const execRes = await safeExec("apt", ["list", "--upgradable"], {
1539
+ timeoutMs: options?.timeoutMs ?? 2e4
1540
+ });
1541
+ const updates = [];
1542
+ const lines = execRes.stdout.split("\n");
1543
+ for (const line of lines) {
1544
+ if (!line.includes("/") || line.startsWith("Listing")) continue;
1545
+ const match = line.match(/^([^\/\s]+)\/\S+\s+(\S+)\s+\S+\s+\[upgradable from:\s+(\S+)\]/);
1546
+ if (match) {
1547
+ const [, name, latest, current] = match;
1548
+ updates.push({
1549
+ managerId: this.id,
1550
+ managerName: this.name,
1551
+ name,
1552
+ currentVersion: current,
1553
+ latestVersion: latest,
1554
+ type: "deb"
1555
+ });
1556
+ }
1557
+ }
1558
+ return {
1559
+ managerId: this.id,
1560
+ managerName: this.name,
1561
+ icon: this.icon,
1562
+ category: this.category,
1563
+ available: true,
1564
+ updates,
1565
+ durationMs: Date.now() - startTime
1566
+ };
1567
+ } catch (err) {
1568
+ return {
1569
+ managerId: this.id,
1570
+ managerName: this.name,
1571
+ icon: this.icon,
1572
+ category: this.category,
1573
+ available: true,
1574
+ updates: [],
1575
+ durationMs: Date.now() - startTime,
1576
+ error: err instanceof Error ? err.message : String(err)
1577
+ };
1578
+ }
1579
+ }
1580
+ async executeUpdate(items, options) {
1581
+ const startTime = Date.now();
1582
+ const steps = [];
1583
+ if (!options.dryRun && process.getuid?.() !== 0) {
1584
+ const sudoCheck = await safeExec("sudo", ["-n", "true"], { timeoutMs: 3e3 });
1585
+ if (!sudoCheck.success) {
1586
+ return {
1587
+ managerId: this.id,
1588
+ managerName: this.name,
1589
+ icon: this.icon,
1590
+ success: false,
1591
+ updatedCount: 0,
1592
+ durationMs: Date.now() - startTime,
1593
+ steps: [
1594
+ {
1595
+ name: "sudo privileges",
1596
+ command: "sudo -n true",
1597
+ status: "failed",
1598
+ error: 'APT requires sudo privileges. Run "sudo -v" in your terminal before hmd.'
1599
+ }
1600
+ ],
1601
+ error: 'APT requires sudo privileges. Run "sudo -v" before running hmd.'
1602
+ };
1603
+ }
1604
+ }
1605
+ const updateStep = await this.executeStep(
1606
+ "sudo apt-get update",
1607
+ "sudo",
1608
+ ["apt-get", "update", "-y"],
1609
+ options
1610
+ );
1611
+ steps.push(updateStep);
1612
+ const upgradeStep = await this.executeStep(
1613
+ "sudo apt-get upgrade",
1614
+ "sudo",
1615
+ ["apt-get", "upgrade", "-y"],
1616
+ options
1617
+ );
1618
+ steps.push(upgradeStep);
1619
+ const cleanupStep = await this.executeStep(
1620
+ "sudo apt-get autoremove",
1621
+ "sudo",
1622
+ ["apt-get", "autoremove", "-y"],
1623
+ options
1624
+ );
1625
+ steps.push(cleanupStep);
1626
+ const success = steps.every((s) => s.status === "success");
1627
+ return {
1628
+ managerId: this.id,
1629
+ managerName: this.name,
1630
+ icon: this.icon,
1631
+ success,
1632
+ updatedCount: success ? items.length : 0,
1633
+ durationMs: Date.now() - startTime,
1634
+ steps,
1635
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
1636
+ };
1637
+ }
1638
+ };
1639
+ var FlatpakManager = class extends BasePackageManager {
1640
+ id = "flatpak";
1641
+ name = "Flatpak";
1642
+ icon = "\u{1F4E6}";
1643
+ category = "system";
1644
+ binary = "flatpak";
1645
+ async checkUpdates(options) {
1646
+ const startTime = Date.now();
1647
+ const isAvail = await this.isAvailable();
1648
+ if (!isAvail) {
1649
+ return {
1650
+ managerId: this.id,
1651
+ managerName: this.name,
1652
+ icon: this.icon,
1653
+ category: this.category,
1654
+ available: false,
1655
+ updates: [],
1656
+ durationMs: 0
1657
+ };
1658
+ }
1659
+ try {
1660
+ const execRes = await safeExec("flatpak", ["remote-ls", "--updates"], {
1661
+ timeoutMs: options?.timeoutMs ?? 2e4
1662
+ });
1663
+ const updates = [];
1664
+ const lines = execRes.stdout.split("\n");
1665
+ for (const line of lines) {
1666
+ const trimmed = line.trim();
1667
+ if (!trimmed) continue;
1668
+ const parts = trimmed.split(/\s+/);
1669
+ updates.push({
1670
+ managerId: this.id,
1671
+ managerName: this.name,
1672
+ name: parts[0],
1673
+ currentVersion: "installed",
1674
+ latestVersion: parts[1] ?? "latest",
1675
+ type: "flatpak"
1676
+ });
1677
+ }
1678
+ return {
1679
+ managerId: this.id,
1680
+ managerName: this.name,
1681
+ icon: this.icon,
1682
+ category: this.category,
1683
+ available: true,
1684
+ updates,
1685
+ durationMs: Date.now() - startTime
1686
+ };
1687
+ } catch (err) {
1688
+ return {
1689
+ managerId: this.id,
1690
+ managerName: this.name,
1691
+ icon: this.icon,
1692
+ category: this.category,
1693
+ available: true,
1694
+ updates: [],
1695
+ durationMs: Date.now() - startTime,
1696
+ error: err instanceof Error ? err.message : String(err)
1697
+ };
1698
+ }
1699
+ }
1700
+ async executeUpdate(items, options) {
1701
+ const startTime = Date.now();
1702
+ const steps = [];
1703
+ const updateStep = await this.executeStep(
1704
+ "flatpak update -y",
1705
+ "flatpak",
1706
+ ["update", "-y", "--noninteractive"],
1707
+ options
1708
+ );
1709
+ steps.push(updateStep);
1710
+ const success = steps.every((s) => s.status === "success");
1711
+ return {
1712
+ managerId: this.id,
1713
+ managerName: this.name,
1714
+ icon: this.icon,
1715
+ success,
1716
+ updatedCount: success ? items.length : 0,
1717
+ durationMs: Date.now() - startTime,
1718
+ steps,
1719
+ error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
1720
+ };
1721
+ }
1722
+ };
1723
+
1724
+ // src/managers/registry.ts
1725
+ function createDefaultManagers() {
1726
+ return [
1727
+ new HomebrewManager(),
1728
+ new NpmManager(),
1729
+ new PnpmManager(),
1730
+ new BunManager(),
1731
+ new YarnManager(),
1732
+ new PythonPipManager(),
1733
+ new PipxManager(),
1734
+ new CargoManager(),
1735
+ new RubyGemManager(),
1736
+ new MacPortsManager(),
1737
+ new MacAppStoreManager(),
1738
+ new AptManager(),
1739
+ new FlatpakManager()
1740
+ ];
1741
+ }
1742
+ function filterManagers(managers, only, exclude) {
1743
+ let result = [...managers];
1744
+ if (only && only.length > 0) {
1745
+ const onlySet = new Set(only.map((s) => s.toLowerCase().trim()));
1746
+ result = result.filter(
1747
+ (m) => onlySet.has(m.id.toLowerCase()) || onlySet.has(m.name.toLowerCase())
1748
+ );
1749
+ }
1750
+ if (exclude && exclude.length > 0) {
1751
+ const excludeSet = new Set(exclude.map((s) => s.toLowerCase().trim()));
1752
+ result = result.filter(
1753
+ (m) => !excludeSet.has(m.id.toLowerCase()) && !excludeSet.has(m.name.toLowerCase())
1754
+ );
1755
+ }
1756
+ return result;
1757
+ }
1758
+
1759
+ // src/core/engine.ts
1760
+ var UpdaterEngine = class {
1761
+ constructor(managers) {
1762
+ this.managers = managers;
1763
+ }
1764
+ managers;
1765
+ /**
1766
+ * Scans all managers for availability and pending updates.
1767
+ * Runs checks concurrently for high performance, but isolates failures.
1768
+ */
1769
+ async scan(timeoutMs = 25e3, callbacks) {
1770
+ const availabilityChecks = await Promise.all(
1771
+ this.managers.map(async (manager) => {
1772
+ try {
1773
+ const available = await manager.isAvailable();
1774
+ return { manager, available };
1775
+ } catch {
1776
+ return { manager, available: false };
1777
+ }
1778
+ })
1779
+ );
1780
+ const availableManagers = availabilityChecks.filter((c) => c.available).map((c) => c.manager);
1781
+ const unavailableManagers = availabilityChecks.filter((c) => !c.available).map((c) => c.manager);
1782
+ const checkTask = async (manager) => {
1783
+ callbacks?.onManagerStart?.(manager);
1784
+ try {
1785
+ const res = await manager.checkUpdates({ timeoutMs });
1786
+ callbacks?.onManagerComplete?.(manager, res);
1787
+ return res;
1788
+ } catch (err) {
1789
+ const fallbackRes = {
1790
+ managerId: manager.id,
1791
+ managerName: manager.name,
1792
+ icon: manager.icon,
1793
+ category: manager.category,
1794
+ available: true,
1795
+ updates: [],
1796
+ durationMs: 0,
1797
+ error: err instanceof Error ? err.message : String(err)
1798
+ };
1799
+ callbacks?.onManagerComplete?.(manager, fallbackRes);
1800
+ return fallbackRes;
1801
+ }
1802
+ };
1803
+ const checkResults = new Array(availableManagers.length);
1804
+ let taskIndex = 0;
1805
+ const concurrency = Math.min(4, availableManagers.length);
1806
+ const workers = Array.from({ length: concurrency }, async () => {
1807
+ while (taskIndex < availableManagers.length) {
1808
+ const idx = taskIndex++;
1809
+ checkResults[idx] = await checkTask(availableManagers[idx]);
1810
+ }
1811
+ });
1812
+ await Promise.all(workers);
1813
+ const allUpdates = [];
1814
+ for (const res of checkResults) {
1815
+ allUpdates.push(...res.updates);
1816
+ }
1817
+ return {
1818
+ availableResults: checkResults,
1819
+ allUpdates,
1820
+ unavailableManagers
1821
+ };
1822
+ }
1823
+ /**
1824
+ * Executes updates for the specified managers sequentially or with controlled steps.
1825
+ */
1826
+ async execute(selectedManagerIds, itemsByManager, options, onManagerStart, onManagerComplete) {
1827
+ const startTime = Date.now();
1828
+ const results = [];
1829
+ const managersToRun = this.managers.filter(
1830
+ (m) => selectedManagerIds.includes(m.id)
1831
+ );
1832
+ for (const manager of managersToRun) {
1833
+ const items = itemsByManager.get(manager.id) ?? [];
1834
+ onManagerStart?.(manager);
1835
+ try {
1836
+ const result = await manager.executeUpdate(items, options);
1837
+ results.push(result);
1838
+ onManagerComplete?.(manager, result);
1839
+ } catch (err) {
1840
+ const failedResult = {
1841
+ managerId: manager.id,
1842
+ managerName: manager.name,
1843
+ icon: manager.icon,
1844
+ success: false,
1845
+ updatedCount: 0,
1846
+ durationMs: 0,
1847
+ steps: [],
1848
+ error: err instanceof Error ? err.message : String(err)
1849
+ };
1850
+ results.push(failedResult);
1851
+ onManagerComplete?.(manager, failedResult);
1852
+ }
1853
+ }
1854
+ const successful = results.filter((r) => r.success).length;
1855
+ const failed = results.filter((r) => !r.success).length;
1856
+ const totalUpdated = results.reduce((acc, r) => acc + r.updatedCount, 0);
1857
+ const totalPending = Array.from(itemsByManager.values()).reduce(
1858
+ (acc, items) => acc + items.length,
1859
+ 0
1860
+ );
1861
+ return {
1862
+ scannedManagersCount: this.managers.length,
1863
+ availableManagersCount: managersToRun.length,
1864
+ upToDateManagersCount: results.filter((r) => r.success && r.updatedCount === 0).length,
1865
+ pendingUpdatesCount: totalPending,
1866
+ executedCount: results.length,
1867
+ successCount: successful,
1868
+ failedCount: failed,
1869
+ totalDurationMs: Date.now() - startTime,
1870
+ results
1871
+ };
1872
+ }
1873
+ };
1874
+
1875
+ // src/utils/formatting.ts
1876
+ import pc from "picocolors";
1877
+ import Table from "cli-table3";
1878
+ function formatDuration(ms) {
1879
+ if (ms < 1e3) return `${ms}ms`;
1880
+ const seconds = (ms / 1e3).toFixed(1);
1881
+ return `${seconds}s`;
1882
+ }
1883
+ function renderUpdatesTable(items) {
1884
+ if (items.length === 0) {
1885
+ return pc.dim(" No updates pending.");
1886
+ }
1887
+ const table = new Table({
1888
+ head: [
1889
+ pc.dim("Manager"),
1890
+ pc.dim("Package"),
1891
+ pc.dim("Type"),
1892
+ pc.dim("Version Transition")
1893
+ ],
1894
+ style: {
1895
+ head: [],
1896
+ border: ["dim"]
1897
+ },
1898
+ chars: {
1899
+ top: "\u2500",
1900
+ "top-mid": "\u2500",
1901
+ "top-left": "\u250C",
1902
+ "top-right": "\u2510",
1903
+ bottom: "\u2500",
1904
+ "bottom-mid": "\u2500",
1905
+ "bottom-left": "\u2514",
1906
+ "bottom-right": "\u2518",
1907
+ left: "\u2502",
1908
+ "left-mid": "\u251C",
1909
+ mid: "\u2500",
1910
+ "mid-mid": "\u253C",
1911
+ right: "\u2502",
1912
+ "right-mid": "\u2524",
1913
+ middle: "\u2502"
1914
+ }
1915
+ });
1916
+ for (const item of items) {
1917
+ const typeLabel = item.type ? pc.dim(`[${item.type}]`) : pc.dim("-");
1918
+ const currentVer = pc.yellow(item.currentVersion || "?");
1919
+ const arrow = pc.dim(" \u2192 ");
1920
+ const latestVer = pc.green(pc.bold(item.latestVersion || "?"));
1921
+ table.push([
1922
+ pc.cyan(item.managerName),
1923
+ pc.bold(pc.white(item.name)),
1924
+ typeLabel,
1925
+ `${currentVer}${arrow}${latestVer}`
1926
+ ]);
1927
+ }
1928
+ return table.toString();
1929
+ }
1930
+ function renderExecutionSummary(results) {
1931
+ const total = results.length;
1932
+ const successful = results.filter((r) => r.success).length;
1933
+ const failed = results.filter((r) => !r.success).length;
1934
+ const totalUpdated = results.reduce((acc, r) => acc + r.updatedCount, 0);
1935
+ const lines = [];
1936
+ lines.push("");
1937
+ lines.push(pc.dim("\u2500".repeat(60)));
1938
+ lines.push(` ${pc.bold(pc.cyan("Heimdall"))} ${pc.dim("\u2022 Execution Summary")}`);
1939
+ lines.push(pc.dim("\u2500".repeat(60)));
1940
+ for (const res of results) {
1941
+ const icon = res.icon || "\u{1F4E6}";
1942
+ const status = res.success ? pc.green("\u2714 SUCCESS") : pc.red("\u2716 FAILED");
1943
+ const time = pc.dim(`(${formatDuration(res.durationMs)})`);
1944
+ const count = res.success ? pc.dim(`\u2022 ${res.updatedCount} updated`) : pc.dim(`\u2022 ${res.updatedCount} updated, ${res.steps.filter((s) => s.status === "failed").length} issue(s)`);
1945
+ lines.push(` ${icon} ${pc.bold(res.managerName)} ${status} ${count} ${time}`);
1946
+ if (!res.success && res.error) {
1947
+ lines.push(` ${pc.red("\u21B3 Error:")} ${pc.dim(res.error.trim().split("\n")[0])}`);
1948
+ }
1949
+ if (res.steps.length > 0) {
1950
+ for (const step of res.steps) {
1951
+ if (step.status === "skipped") continue;
1952
+ const stepStatus = step.status === "success" ? pc.green("\u2714") : step.status === "failed" ? pc.red("\u2716") : pc.dim("\u25CB");
1953
+ const stepDuration = step.durationMs ? pc.dim(`[${formatDuration(step.durationMs)}]`) : "";
1954
+ const stepError = step.error && step.status === "failed" ? pc.red(` (${step.error.split("\n")[0]})`) : "";
1955
+ lines.push(` ${stepStatus} ${pc.dim(step.name)} ${stepDuration}${stepError}`);
1956
+ }
1957
+ }
1958
+ }
1959
+ lines.push(pc.dim("\u2500".repeat(60)));
1960
+ const summaryParts = [
1961
+ pc.green(`${successful}/${total} managers succeeded`),
1962
+ failed > 0 ? pc.red(`${failed} failed`) : null,
1963
+ pc.cyan(`${totalUpdated} package(s) updated`)
1964
+ ].filter(Boolean);
1965
+ lines.push(` ${summaryParts.join(" \u2022 ")}`);
1966
+ lines.push(pc.dim("\u2500".repeat(60)));
1967
+ return lines.join("\n");
1968
+ }
1969
+
1970
+ // src/utils/version.ts
1971
+ import { fileURLToPath } from "url";
1972
+ import { dirname, resolve } from "path";
1973
+ import { existsSync, readFileSync } from "fs";
1974
+ var FALLBACK_METADATA = {
1975
+ name: "@dahch/heimdall",
1976
+ version: "1.0.0",
1977
+ description: "Heimdall: resilient validator and updater for system and global packages"
1978
+ };
1979
+ var cachedMetadata = null;
1980
+ var customFsAdapter = null;
1981
+ function resolvePackageMetadata(options) {
1982
+ const exists = options?.fsAdapter?.existsSync ?? customFsAdapter?.existsSync ?? existsSync;
1983
+ const read = options?.fsAdapter?.readFileSync ?? customFsAdapter?.readFileSync ?? readFileSync;
1984
+ try {
1985
+ let currentDir;
1986
+ if (options?.basePath) {
1987
+ currentDir = options.basePath;
1988
+ } else {
1989
+ try {
1990
+ if (typeof import.meta !== "undefined" && import.meta.url) {
1991
+ currentDir = dirname(fileURLToPath(import.meta.url));
1992
+ }
1993
+ } catch {
1994
+ }
1995
+ }
1996
+ const candidates = [];
1997
+ if (currentDir) {
1998
+ candidates.push(
1999
+ resolve(currentDir, "../package.json"),
2000
+ resolve(currentDir, "../../package.json"),
2001
+ resolve(currentDir, "./package.json")
2002
+ );
2003
+ }
2004
+ for (const candidate of candidates) {
2005
+ try {
2006
+ if (exists(candidate)) {
2007
+ const raw = read(candidate, "utf-8");
2008
+ const parsed = JSON.parse(raw);
2009
+ if (parsed && typeof parsed === "object") {
2010
+ const name = typeof parsed.name === "string" && parsed.name.trim().length > 0 ? parsed.name.trim() : FALLBACK_METADATA.name;
2011
+ const version = typeof parsed.version === "string" && parsed.version.trim().length > 0 ? parsed.version.trim() : FALLBACK_METADATA.version;
2012
+ const description = typeof parsed.description === "string" && parsed.description.trim().length > 0 ? parsed.description.trim() : FALLBACK_METADATA.description;
2013
+ return { name, version, description };
2014
+ }
2015
+ }
2016
+ } catch {
2017
+ continue;
2018
+ }
2019
+ }
2020
+ } catch {
2021
+ }
2022
+ return { ...FALLBACK_METADATA };
2023
+ }
2024
+ function getPackageMetadata(options) {
2025
+ if (!options?.bypassCache && cachedMetadata && !options?.basePath) {
2026
+ return cachedMetadata;
2027
+ }
2028
+ const meta = resolvePackageMetadata(options);
2029
+ if (!options?.basePath) {
2030
+ cachedMetadata = meta;
2031
+ }
2032
+ return meta;
2033
+ }
2034
+
2035
+ // src/cli.ts
2036
+ async function main() {
2037
+ const { version, description } = getPackageMetadata();
2038
+ const program = new Command();
2039
+ program.name("hmd").description(description || "Heimdall: resilient validator and updater for system and global packages").version(version).option("-y, --yes", "Skip confirmation prompt and update all automatically").option("-d, --dry-run", "Inspect outdated packages and simulate update without running changes").option("-o, --only <managers>", "Only check and update specified managers (comma-separated, e.g. brew,npm)").option("-x, --exclude <managers>", "Exclude specified managers (comma-separated, e.g. macports,pip)").option("-t, --timeout <ms>", "Timeout per check in milliseconds", "25000").option("-v, --verbose", "Show detailed output and logs").parse(process.argv);
2040
+ const options = program.opts();
2041
+ if (process.stdout.isTTY && !options.verbose) {
2042
+ console.clear();
2043
+ }
2044
+ p.intro(`${pc2.cyan("\u25CF")} ${pc2.bold("hmd")} ${pc2.dim(`v${version}`)} \u2014 ${pc2.dim("Heimdall System Updater")}`);
2045
+ const rawTimeout = parseInt(options.timeout || "25000", 10);
2046
+ const timeoutMs = Number.isFinite(rawTimeout) && rawTimeout > 0 ? rawTimeout : 25e3;
2047
+ const onlyList = options.only ? options.only.split(",") : void 0;
2048
+ const excludeList = options.exclude ? options.exclude.split(",") : void 0;
2049
+ const allManagers = createDefaultManagers();
2050
+ const selectedManagers = filterManagers(allManagers, onlyList, excludeList);
2051
+ if (selectedManagers.length === 0) {
2052
+ p.log.error(pc2.red("No package managers matched your filter."));
2053
+ p.outro(pc2.yellow("Exiting without changes."));
2054
+ process.exit(1);
2055
+ }
2056
+ const engine = new UpdaterEngine(selectedManagers);
2057
+ const scanSpinner = p.spinner();
2058
+ scanSpinner.start("Discovering available package managers and checking for updates...");
2059
+ let activeChecks = 0;
2060
+ const { availableResults, allUpdates, unavailableManagers } = await engine.scan(timeoutMs, {
2061
+ onManagerStart: (mgr) => {
2062
+ activeChecks++;
2063
+ scanSpinner.message(`Querying ${mgr.icon} ${mgr.name} (${activeChecks} in progress)...`);
2064
+ },
2065
+ onManagerComplete: (mgr, res) => {
2066
+ activeChecks = Math.max(0, activeChecks - 1);
2067
+ const countStr = res.updates.length > 0 ? pc2.yellow(`${res.updates.length} outdated`) : pc2.green("up to date");
2068
+ scanSpinner.message(`Checked ${mgr.icon} ${mgr.name}: ${countStr}`);
2069
+ }
2070
+ });
2071
+ scanSpinner.stop("Discovery and validation completed");
2072
+ const availableManagers = availableResults.map((r) => `${r.icon} ${pc2.bold(r.managerName)}`);
2073
+ p.log.info(
2074
+ `${pc2.bold("Active package managers:")} ${availableManagers.join(" \u2022 ")}`
2075
+ );
2076
+ if (unavailableManagers.length > 0 && options.verbose) {
2077
+ const unavailStr = unavailableManagers.map((m) => `${m.icon} ${m.name}`).join(", ");
2078
+ p.log.step(pc2.dim(`Inactive/not installed (skipped safely): ${unavailStr}`));
2079
+ }
2080
+ if (allUpdates.length === 0) {
2081
+ p.note(
2082
+ pc2.green("All detected package managers and global packages are completely up to date! \u{1F389}"),
2083
+ "Status: Up to Date"
2084
+ );
2085
+ p.outro(pc2.cyan("No updates needed. Have a great day!"));
2086
+ process.exit(0);
2087
+ }
2088
+ p.log.message(`
2089
+ ${pc2.bold("Pending updates")} ${pc2.dim(`(${allUpdates.length} total):`)}`);
2090
+ console.log(renderUpdatesTable(allUpdates));
2091
+ console.log("");
2092
+ const itemsByManager = /* @__PURE__ */ new Map();
2093
+ for (const update of allUpdates) {
2094
+ const list = itemsByManager.get(update.managerId) ?? [];
2095
+ list.push(update);
2096
+ itemsByManager.set(update.managerId, list);
2097
+ }
2098
+ let managersToUpdate = [];
2099
+ if (options.dryRun) {
2100
+ managersToUpdate = Array.from(itemsByManager.keys());
2101
+ p.log.info(pc2.yellow(`[DRY RUN] Simulating updates for ${allUpdates.length} package(s)...`));
2102
+ } else if (options.yes) {
2103
+ managersToUpdate = Array.from(itemsByManager.keys());
2104
+ p.log.info(pc2.cyan("Flag -y provided: proceeding with all updates automatically."));
2105
+ } else {
2106
+ const managerOptions = Array.from(itemsByManager.entries()).map(([mgrId, items]) => {
2107
+ const mgrInfo = availableResults.find((r) => r.managerId === mgrId);
2108
+ return {
2109
+ value: mgrId,
2110
+ label: `${mgrInfo?.icon ?? "\u{1F4E6}"} ${mgrInfo?.managerName ?? mgrId} (${items.length} updates)`
2111
+ };
2112
+ });
2113
+ const action = await p.select({
2114
+ message: `How would you like to proceed with the ${allUpdates.length} update(s)?`,
2115
+ options: [
2116
+ {
2117
+ value: "all",
2118
+ label: `\u26A1 Confirm & Update everything (${allUpdates.length} packages across ${itemsByManager.size} managers)`,
2119
+ hint: "Recommended"
2120
+ },
2121
+ {
2122
+ value: "select",
2123
+ label: "\u2699 Select specific package managers to update",
2124
+ hint: "Fine-grained choice"
2125
+ },
2126
+ {
2127
+ value: "cancel",
2128
+ label: "\u2716 Cancel and exit without updating"
2129
+ }
2130
+ ]
2131
+ });
2132
+ if (p.isCancel(action) || action === "cancel") {
2133
+ p.cancel("Update operation cancelled by user.");
2134
+ process.exit(0);
2135
+ }
2136
+ if (action === "all") {
2137
+ managersToUpdate = Array.from(itemsByManager.keys());
2138
+ } else if (action === "select") {
2139
+ const chosen = await p.multiselect({
2140
+ message: "Select the package managers you want to update:",
2141
+ options: managerOptions,
2142
+ initialValues: Array.from(itemsByManager.keys()),
2143
+ required: true
2144
+ });
2145
+ if (p.isCancel(chosen)) {
2146
+ p.cancel("Selection cancelled.");
2147
+ process.exit(0);
2148
+ }
2149
+ managersToUpdate = chosen;
2150
+ }
2151
+ }
2152
+ if (managersToUpdate.length === 0) {
2153
+ p.outro(pc2.yellow("No managers selected for update."));
2154
+ process.exit(0);
2155
+ }
2156
+ const isSimulated = options.dryRun ?? false;
2157
+ p.log.step(
2158
+ pc2.bold(
2159
+ pc2.cyan(
2160
+ `
2161
+ ${isSimulated ? "[DRY RUN] Simulating" : "Starting"} updates for ${managersToUpdate.length} package manager(s)...`
2162
+ )
2163
+ )
2164
+ );
2165
+ const updateSpinner = p.spinner();
2166
+ let managerIndex = 0;
2167
+ const summary = await engine.execute(
2168
+ managersToUpdate,
2169
+ itemsByManager,
2170
+ {
2171
+ verbose: options.verbose,
2172
+ dryRun: isSimulated,
2173
+ onStepStart: (stepName) => {
2174
+ updateSpinner.message(pc2.dim(`Running: ${stepName}...`));
2175
+ },
2176
+ onStepProgress: (stepName, logLine) => {
2177
+ if (logLine) {
2178
+ const truncated = logLine.length > 55 ? logLine.slice(0, 52) + "..." : logLine;
2179
+ updateSpinner.message(pc2.dim(`${stepName} [${truncated}]`));
2180
+ }
2181
+ },
2182
+ onStepEnd: (stepName, success, error, durationMs) => {
2183
+ const mark = success ? pc2.green("\u2714") : pc2.red("\u2716");
2184
+ const durStr = durationMs !== void 0 ? pc2.dim(` [${formatDuration(durationMs)}]`) : "";
2185
+ const detail = error ? pc2.red(` \u21B3 ${error.split("\n")[0]}`) : "";
2186
+ updateSpinner.stop(` ${mark} ${pc2.dim(stepName)}${durStr}${detail}`);
2187
+ updateSpinner.start(pc2.dim("Working..."));
2188
+ }
2189
+ },
2190
+ (mgr) => {
2191
+ managerIndex++;
2192
+ console.log(pc2.bold(pc2.cyan(`
2193
+ \u25CF [${managerIndex}/${managersToUpdate.length}] ${mgr.icon} ${mgr.name}`)));
2194
+ updateSpinner.start(pc2.dim("Starting update routine..."));
2195
+ },
2196
+ (mgr, res) => {
2197
+ if (res.success) {
2198
+ updateSpinner.stop(pc2.green(` \u2714 ${mgr.name} completed in ${formatDuration(res.durationMs)}`));
2199
+ } else {
2200
+ updateSpinner.stop(pc2.red(` \u2716 ${mgr.name} completed with issues in ${formatDuration(res.durationMs)}`));
2201
+ }
2202
+ }
2203
+ );
2204
+ console.log(renderExecutionSummary(summary.results));
2205
+ if (summary.failedCount === 0) {
2206
+ p.outro(
2207
+ pc2.bold(
2208
+ pc2.green(
2209
+ `\u2714 All operations completed successfully: ${summary.pendingUpdatesCount} package(s) updated across ${summary.successCount} manager(s) (${formatDuration(summary.totalDurationMs)})!`
2210
+ )
2211
+ )
2212
+ );
2213
+ } else {
2214
+ process.exitCode = 1;
2215
+ p.outro(
2216
+ pc2.bold(
2217
+ pc2.yellow(
2218
+ `Completed with warnings: ${summary.successCount} succeeded, ${summary.failedCount} failed (${formatDuration(summary.totalDurationMs)}).`
2219
+ )
2220
+ )
2221
+ );
2222
+ }
2223
+ }
2224
+ main().catch((err) => {
2225
+ p.log.error(pc2.red(`Fatal error: ${err instanceof Error ? err.message : String(err)}`));
2226
+ process.exit(1);
2227
+ });