brownian-code 2026.2.12 → 2026.2.13
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/package.json +1 -1
- package/src/index.tsx +5 -0
- package/src/utils/update-checker.ts +110 -0
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -21,6 +21,7 @@ import React from 'react';
|
|
|
21
21
|
import { render } from 'ink';
|
|
22
22
|
import { config } from 'dotenv';
|
|
23
23
|
import { CLI } from './cli.js';
|
|
24
|
+
import { checkForUpdate } from './utils/update-checker.js';
|
|
24
25
|
|
|
25
26
|
// Handle --version / -v
|
|
26
27
|
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
@@ -65,6 +66,10 @@ More info: https://brownian.xyz
|
|
|
65
66
|
process.exit(0);
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
// Check for updates (non-blocking, best-effort)
|
|
70
|
+
const updateMsg = checkForUpdate(projectRoot);
|
|
71
|
+
if (updateMsg) console.log(`\n ${updateMsg}\n`);
|
|
72
|
+
|
|
68
73
|
// Load environment variables
|
|
69
74
|
config({ quiet: true });
|
|
70
75
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
const PACKAGE_NAME = 'brownian-code';
|
|
6
|
+
const CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
|
|
7
|
+
|
|
8
|
+
interface UpdateCache {
|
|
9
|
+
lastCheck: number;
|
|
10
|
+
latestVersion: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getCacheDir(): string {
|
|
14
|
+
const home = process.env.HOME || process.env.USERPROFILE || '/tmp';
|
|
15
|
+
const dir = join(home, '.brownian');
|
|
16
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
17
|
+
return dir;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getCachePath(): string {
|
|
21
|
+
return join(getCacheDir(), 'update-check.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readCache(): UpdateCache | null {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(getCachePath(), 'utf-8'));
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writeCache(cache: UpdateCache): void {
|
|
33
|
+
try {
|
|
34
|
+
writeFileSync(getCachePath(), JSON.stringify(cache));
|
|
35
|
+
} catch {
|
|
36
|
+
// Silently fail — update check is best-effort
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getCurrentVersion(projectRoot: string): string | null {
|
|
41
|
+
try {
|
|
42
|
+
const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8'));
|
|
43
|
+
return pkg.version ?? null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function fetchLatestVersion(): string | null {
|
|
50
|
+
try {
|
|
51
|
+
const result = execSync(`npm view ${PACKAGE_NAME} version`, {
|
|
52
|
+
timeout: 5000,
|
|
53
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
54
|
+
encoding: 'utf-8',
|
|
55
|
+
});
|
|
56
|
+
return result.trim() || null;
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isNewer(latest: string, current: string): boolean {
|
|
63
|
+
const lParts = latest.split('.').map(Number);
|
|
64
|
+
const cParts = current.split('.').map(Number);
|
|
65
|
+
for (let i = 0; i < Math.max(lParts.length, cParts.length); i++) {
|
|
66
|
+
const l = lParts[i] ?? 0;
|
|
67
|
+
const c = cParts[i] ?? 0;
|
|
68
|
+
if (l > c) return true;
|
|
69
|
+
if (l < c) return false;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check for updates in the background. Returns an update message if a
|
|
76
|
+
* newer version is available, or null. Never throws.
|
|
77
|
+
*/
|
|
78
|
+
export function checkForUpdate(projectRoot: string): string | null {
|
|
79
|
+
try {
|
|
80
|
+
const current = getCurrentVersion(projectRoot);
|
|
81
|
+
if (!current) return null;
|
|
82
|
+
|
|
83
|
+
const cache = readCache();
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
|
|
86
|
+
// Use cached result if recent enough
|
|
87
|
+
if (cache && now - cache.lastCheck < CHECK_INTERVAL_MS) {
|
|
88
|
+
if (cache.latestVersion && isNewer(cache.latestVersion, current)) {
|
|
89
|
+
return formatMessage(current, cache.latestVersion);
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Fetch fresh version from registry
|
|
95
|
+
const latest = fetchLatestVersion();
|
|
96
|
+
writeCache({ lastCheck: now, latestVersion: latest });
|
|
97
|
+
|
|
98
|
+
if (latest && isNewer(latest, current)) {
|
|
99
|
+
return formatMessage(current, latest);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return null;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function formatMessage(current: string, latest: string): string {
|
|
109
|
+
return `Update available: v${current} → v${latest}. Run: bun add -g brownian-code@latest`;
|
|
110
|
+
}
|