@brainjar/cli 0.4.0 → 0.5.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.
package/src/upgrade.ts ADDED
@@ -0,0 +1,121 @@
1
+ import { execFile } from 'node:child_process'
2
+ import {
3
+ healthCheck,
4
+ start,
5
+ stop,
6
+ status as daemonStatus,
7
+ upgradeServer,
8
+ } from './daemon.js'
9
+ import { checkForUpdates } from './version-check.js'
10
+ import { ErrorCode, createError } from './errors.js'
11
+ import pkg from '../package.json'
12
+
13
+ export interface ComponentResult {
14
+ upgraded: boolean
15
+ from: string
16
+ to: string
17
+ message?: string
18
+ }
19
+
20
+ export interface ServerResult extends ComponentResult {
21
+ restarted?: boolean
22
+ }
23
+
24
+ export interface UpgradeResult {
25
+ cli?: ComponentResult
26
+ server?: ServerResult
27
+ }
28
+
29
+ /**
30
+ * Detect which package manager installed brainjar.
31
+ * Checks the runtime binary path first, then falls back to npm.
32
+ */
33
+ export function detectPackageManager(): 'bun' | 'npm' {
34
+ const argv0 = process.argv[0] ?? ''
35
+ if (argv0.includes('bun')) return 'bun'
36
+ return 'npm'
37
+ }
38
+
39
+ /** Shell out to a package manager and capture stdout/stderr. */
40
+ function exec(cmd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {
41
+ return new Promise((resolve, reject) => {
42
+ execFile(cmd, args, { timeout: 120_000 }, (error, stdout, stderr) => {
43
+ if (error) reject(Object.assign(error, { stderr }))
44
+ else resolve({ stdout, stderr })
45
+ })
46
+ })
47
+ }
48
+
49
+ /**
50
+ * Upgrade the CLI npm package to latest.
51
+ */
52
+ export async function upgradeCli(): Promise<ComponentResult> {
53
+ const currentVersion = pkg.version
54
+
55
+ // Check if already on latest
56
+ const updates = await checkForUpdates(currentVersion)
57
+ if (!updates?.cli) {
58
+ return { upgraded: false, from: currentVersion, to: currentVersion, message: 'Already on latest version' }
59
+ }
60
+
61
+ const latestVersion = updates.cli.latest
62
+ const pm = detectPackageManager()
63
+
64
+ try {
65
+ if (pm === 'bun') {
66
+ await exec('bun', ['install', '-g', `@brainjar/cli@${latestVersion}`])
67
+ } else {
68
+ await exec('npm', ['install', '-g', `@brainjar/cli@${latestVersion}`])
69
+ }
70
+ } catch (e: any) {
71
+ const stderr = e.stderr ?? e.message ?? ''
72
+ const isPermission = stderr.includes('EACCES') || stderr.includes('permission')
73
+ throw createError(ErrorCode.SHELL_ERROR, {
74
+ message: `Failed to upgrade CLI via ${pm}: ${stderr.trim()}`,
75
+ hint: isPermission
76
+ ? `Try running with sudo, or fix npm permissions: https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally`
77
+ : 'Check your network connection and try again.',
78
+ })
79
+ }
80
+
81
+ return { upgraded: true, from: currentVersion, to: latestVersion }
82
+ }
83
+
84
+ /**
85
+ * Upgrade the server binary. Handles stop/restart lifecycle.
86
+ */
87
+ export async function upgradeServerBinary(): Promise<ServerResult> {
88
+ const { getInstalledServerVersion } = await import('./version-check.js')
89
+ const installedVersion = (await getInstalledServerVersion()) ?? 'unknown'
90
+
91
+ // Stop server if running before replacing binary
92
+ const s = await daemonStatus()
93
+ const wasRunning = s.running
94
+
95
+ if (wasRunning) {
96
+ await stop()
97
+ }
98
+
99
+ const result = await upgradeServer()
100
+
101
+ if (result.alreadyLatest) {
102
+ if (wasRunning) await start()
103
+ return { upgraded: false, from: installedVersion, to: result.version, message: 'Already on latest version' }
104
+ }
105
+
106
+ // Restart if it was running
107
+ if (wasRunning) {
108
+ await start()
109
+ const deadline = Date.now() + 10_000
110
+ while (Date.now() < deadline) {
111
+ await new Promise(r => setTimeout(r, 200))
112
+ const check = await healthCheck({ timeout: 2000 })
113
+ if (check.healthy) {
114
+ return { upgraded: true, from: installedVersion, to: result.version, restarted: true }
115
+ }
116
+ }
117
+ return { upgraded: true, from: installedVersion, to: result.version, restarted: false, message: 'Upgraded but failed health check after restart' }
118
+ }
119
+
120
+ return { upgraded: true, from: installedVersion, to: result.version }
121
+ }
@@ -126,11 +126,11 @@ export async function renderUpdateBanner(currentCliVersion: string): Promise<str
126
126
  const lines: string[] = []
127
127
 
128
128
  if (updates.cli) {
129
- lines.push(` ⬆ brainjar ${updates.cli.latest} available (current: ${updates.cli.current}) — npm update -g @brainjar/cli`)
129
+ lines.push(` ⬆ brainjar ${updates.cli.latest} available (current: ${updates.cli.current}) — brainjar upgrade`)
130
130
  }
131
131
 
132
132
  if (updates.server) {
133
- lines.push(` ⬆ server ${updates.server.latest} available (current: ${updates.server.current}) — brainjar server upgrade`)
133
+ lines.push(` ⬆ server ${updates.server.latest} available (current: ${updates.server.current}) — brainjar upgrade`)
134
134
  }
135
135
 
136
136
  return lines.length > 0 ? lines.join('\n') : undefined