@blockrun/franklin 3.8.10 → 3.8.11
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/dist/banner.js +20 -2
- package/dist/commands/doctor.js +10 -2
- package/dist/version-check.d.ts +39 -0
- package/dist/version-check.js +134 -0
- package/package.json +1 -1
package/dist/banner.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { kickoffVersionCheck, getAvailableUpdate } from './version-check.js';
|
|
2
3
|
// ─── Ben Franklin portrait ─────────────────────────────────────────────────
|
|
3
4
|
//
|
|
4
5
|
// Generated once, at build time, from the Joseph Duplessis 1785 oil painting
|
|
@@ -85,9 +86,26 @@ export function printBanner(version) {
|
|
|
85
86
|
const style = process.env.FRANKLIN_BANNER?.toLowerCase();
|
|
86
87
|
if (style === 'full' || style === 'legacy') {
|
|
87
88
|
printLegacyBanner(version);
|
|
88
|
-
return;
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
else {
|
|
91
|
+
printCompactBanner(version);
|
|
92
|
+
}
|
|
93
|
+
// Kick off a background refresh for *next* startup, and print a hint now
|
|
94
|
+
// if the cache already knows about a newer version. All wrapped in
|
|
95
|
+
// try/catch because a banner should never be the reason startup breaks.
|
|
96
|
+
try {
|
|
97
|
+
kickoffVersionCheck();
|
|
98
|
+
const update = getAvailableUpdate();
|
|
99
|
+
if (update) {
|
|
100
|
+
console.log(chalk.yellow('⟳ ') +
|
|
101
|
+
chalk.bold(`Franklin ${update.latest}`) +
|
|
102
|
+
chalk.dim(` available — you have ${update.current}`));
|
|
103
|
+
console.log(chalk.dim(' Run: ') +
|
|
104
|
+
chalk.bold('npm install -g @blockrun/franklin@latest'));
|
|
105
|
+
console.log('');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch { /* version-check is best-effort; never block startup */ }
|
|
91
109
|
}
|
|
92
110
|
function printCompactBanner(version) {
|
|
93
111
|
const title = chalk.bold.hex(GOLD_START)('FRANKLIN');
|
package/dist/commands/doctor.js
CHANGED
|
@@ -17,6 +17,7 @@ import os from 'node:os';
|
|
|
17
17
|
import { setupAgentWallet, setupAgentSolanaWallet, } from '@blockrun/llm';
|
|
18
18
|
import { loadChain, API_URLS, VERSION, BLOCKRUN_DIR } from '../config.js';
|
|
19
19
|
import { isTelemetryEnabled, readAllRecords } from '../telemetry/store.js';
|
|
20
|
+
import { getAvailableUpdate, kickoffVersionCheck } from '../version-check.js';
|
|
20
21
|
async function runChecks() {
|
|
21
22
|
const out = [];
|
|
22
23
|
// ── 1. Runtime ────────────────────────────────────────────────────
|
|
@@ -29,10 +30,17 @@ async function runChecks() {
|
|
|
29
30
|
remedy: nodeMajor >= 20 ? undefined : 'Upgrade Node.js: https://nodejs.org',
|
|
30
31
|
});
|
|
31
32
|
// ── 2. Franklin version ───────────────────────────────────────────
|
|
33
|
+
// Kick the daily cache refresh so subsequent doctor runs carry fresh
|
|
34
|
+
// data. Current run uses whatever's already cached.
|
|
35
|
+
kickoffVersionCheck();
|
|
36
|
+
const update = getAvailableUpdate();
|
|
32
37
|
out.push({
|
|
33
38
|
name: 'Franklin',
|
|
34
|
-
status: 'ok',
|
|
35
|
-
detail:
|
|
39
|
+
status: update ? 'warn' : 'ok',
|
|
40
|
+
detail: update
|
|
41
|
+
? `v${VERSION} — update available: v${update.latest}`
|
|
42
|
+
: `v${VERSION}`,
|
|
43
|
+
remedy: update ? 'npm install -g @blockrun/franklin@latest' : undefined,
|
|
36
44
|
});
|
|
37
45
|
// ── 3. BLOCKRUN_DIR writable ──────────────────────────────────────
|
|
38
46
|
try {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update-check utility.
|
|
3
|
+
*
|
|
4
|
+
* Quietly pings npm once per day, caches the latest published version in
|
|
5
|
+
* `~/.blockrun/version-check.json`, and exposes a sync helper the CLI
|
|
6
|
+
* uses to nudge users when they're behind. The check is non-blocking:
|
|
7
|
+
* fire-and-forget at startup, render the notice on the *next* run if the
|
|
8
|
+
* network was slow the first time. Users never wait on it.
|
|
9
|
+
*
|
|
10
|
+
* Respects two opt-outs:
|
|
11
|
+
* - `FRANKLIN_NO_UPDATE_CHECK=1` — explicit user preference
|
|
12
|
+
* - CI-like environments (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, etc.)
|
|
13
|
+
*
|
|
14
|
+
* Cache format is intentionally small and forward-compatible: new fields
|
|
15
|
+
* may be added, old fields are tolerated on read.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Compare two semver strings (stripping a leading `v` and any pre-release
|
|
19
|
+
* tag after a hyphen — we don't publish prereleases). Returns:
|
|
20
|
+
* 1 if a > b
|
|
21
|
+
* -1 if a < b
|
|
22
|
+
* 0 if equal or unparseable
|
|
23
|
+
*/
|
|
24
|
+
export declare function compareSemver(a: string, b: string): number;
|
|
25
|
+
/**
|
|
26
|
+
* Refresh the cache in the background if it's stale. Never throws, never
|
|
27
|
+
* awaited by callers — result lands before next startup.
|
|
28
|
+
*/
|
|
29
|
+
export declare function kickoffVersionCheck(): void;
|
|
30
|
+
export interface UpdateInfo {
|
|
31
|
+
current: string;
|
|
32
|
+
latest: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Sync check against the cached latest. Returns update info if the cache
|
|
36
|
+
* knows of a newer version, null otherwise. Safe to call before the first
|
|
37
|
+
* background check settles — returns null (we don't speculate).
|
|
38
|
+
*/
|
|
39
|
+
export declare function getAvailableUpdate(): UpdateInfo | null;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update-check utility.
|
|
3
|
+
*
|
|
4
|
+
* Quietly pings npm once per day, caches the latest published version in
|
|
5
|
+
* `~/.blockrun/version-check.json`, and exposes a sync helper the CLI
|
|
6
|
+
* uses to nudge users when they're behind. The check is non-blocking:
|
|
7
|
+
* fire-and-forget at startup, render the notice on the *next* run if the
|
|
8
|
+
* network was slow the first time. Users never wait on it.
|
|
9
|
+
*
|
|
10
|
+
* Respects two opt-outs:
|
|
11
|
+
* - `FRANKLIN_NO_UPDATE_CHECK=1` — explicit user preference
|
|
12
|
+
* - CI-like environments (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, etc.)
|
|
13
|
+
*
|
|
14
|
+
* Cache format is intentionally small and forward-compatible: new fields
|
|
15
|
+
* may be added, old fields are tolerated on read.
|
|
16
|
+
*/
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { BLOCKRUN_DIR, VERSION, USER_AGENT } from './config.js';
|
|
20
|
+
const CACHE_FILE = path.join(BLOCKRUN_DIR, 'version-check.json');
|
|
21
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // once per day
|
|
22
|
+
const FETCH_TIMEOUT_MS = 2_000;
|
|
23
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/@blockrun/franklin/latest';
|
|
24
|
+
function isDisabled() {
|
|
25
|
+
if (process.env.FRANKLIN_NO_UPDATE_CHECK === '1')
|
|
26
|
+
return true;
|
|
27
|
+
// Common CI signals — no point nagging headless runners.
|
|
28
|
+
return Boolean(process.env.CI ||
|
|
29
|
+
process.env.GITHUB_ACTIONS ||
|
|
30
|
+
process.env.GITLAB_CI ||
|
|
31
|
+
process.env.BUILDKITE ||
|
|
32
|
+
process.env.CIRCLECI);
|
|
33
|
+
}
|
|
34
|
+
function readCache() {
|
|
35
|
+
try {
|
|
36
|
+
const raw = fs.readFileSync(CACHE_FILE, 'utf-8');
|
|
37
|
+
const parsed = JSON.parse(raw);
|
|
38
|
+
if (typeof parsed.latestVersion === 'string' && typeof parsed.checkedAt === 'number') {
|
|
39
|
+
return { latestVersion: parsed.latestVersion, checkedAt: parsed.checkedAt };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch { /* no cache, bad JSON, first run — all handled by returning null */ }
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function writeCache(data) {
|
|
46
|
+
try {
|
|
47
|
+
fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
|
|
48
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
49
|
+
}
|
|
50
|
+
catch { /* cache write is best-effort — never crash startup over it */ }
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Compare two semver strings (stripping a leading `v` and any pre-release
|
|
54
|
+
* tag after a hyphen — we don't publish prereleases). Returns:
|
|
55
|
+
* 1 if a > b
|
|
56
|
+
* -1 if a < b
|
|
57
|
+
* 0 if equal or unparseable
|
|
58
|
+
*/
|
|
59
|
+
export function compareSemver(a, b) {
|
|
60
|
+
const parse = (v) => {
|
|
61
|
+
const core = v.replace(/^v/, '').split('-')[0];
|
|
62
|
+
const parts = core.split('.').map(n => Number.parseInt(n, 10));
|
|
63
|
+
if (parts.length !== 3 || parts.some(Number.isNaN))
|
|
64
|
+
return null;
|
|
65
|
+
return [parts[0], parts[1], parts[2]];
|
|
66
|
+
};
|
|
67
|
+
const pa = parse(a);
|
|
68
|
+
const pb = parse(b);
|
|
69
|
+
if (!pa || !pb)
|
|
70
|
+
return 0;
|
|
71
|
+
for (let i = 0; i < 3; i++) {
|
|
72
|
+
if (pa[i] > pb[i])
|
|
73
|
+
return 1;
|
|
74
|
+
if (pa[i] < pb[i])
|
|
75
|
+
return -1;
|
|
76
|
+
}
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
async function fetchLatestVersion() {
|
|
80
|
+
const ctrl = new AbortController();
|
|
81
|
+
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(REGISTRY_URL, {
|
|
84
|
+
signal: ctrl.signal,
|
|
85
|
+
headers: { 'User-Agent': USER_AGENT, Accept: 'application/json' },
|
|
86
|
+
});
|
|
87
|
+
if (!res.ok)
|
|
88
|
+
return null;
|
|
89
|
+
const body = (await res.json());
|
|
90
|
+
return typeof body.version === 'string' ? body.version : null;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Refresh the cache in the background if it's stale. Never throws, never
|
|
101
|
+
* awaited by callers — result lands before next startup.
|
|
102
|
+
*/
|
|
103
|
+
export function kickoffVersionCheck() {
|
|
104
|
+
if (isDisabled())
|
|
105
|
+
return;
|
|
106
|
+
const cache = readCache();
|
|
107
|
+
if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS)
|
|
108
|
+
return;
|
|
109
|
+
// Detach from the event loop so startup doesn't wait on network.
|
|
110
|
+
// Node keeps the process alive until the fetch settles, which is fine
|
|
111
|
+
// for short-lived CLI invocations and irrelevant for long-running
|
|
112
|
+
// interactive sessions.
|
|
113
|
+
void fetchLatestVersion().then(latest => {
|
|
114
|
+
if (!latest)
|
|
115
|
+
return;
|
|
116
|
+
writeCache({ latestVersion: latest, checkedAt: Date.now() });
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Sync check against the cached latest. Returns update info if the cache
|
|
121
|
+
* knows of a newer version, null otherwise. Safe to call before the first
|
|
122
|
+
* background check settles — returns null (we don't speculate).
|
|
123
|
+
*/
|
|
124
|
+
export function getAvailableUpdate() {
|
|
125
|
+
if (isDisabled())
|
|
126
|
+
return null;
|
|
127
|
+
const cache = readCache();
|
|
128
|
+
if (!cache)
|
|
129
|
+
return null;
|
|
130
|
+
if (compareSemver(cache.latestVersion, VERSION) > 0) {
|
|
131
|
+
return { current: VERSION, latest: cache.latestVersion };
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
package/package.json
CHANGED