@sylphx/flow 2.15.1 → 2.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/services/auto-upgrade.ts +136 -52
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @sylphx/flow
|
|
2
2
|
|
|
3
|
+
## 2.15.2 (2025-12-17)
|
|
4
|
+
|
|
5
|
+
### ⚡️ Performance
|
|
6
|
+
|
|
7
|
+
- **auto-upgrade:** non-blocking version check with cache ([0550e44](https://github.com/SylphxAI/flow/commit/0550e44ea9b471ae07b2ea13c196354a7a32a605))
|
|
8
|
+
|
|
3
9
|
## 2.15.1 (2025-12-17)
|
|
4
10
|
|
|
5
11
|
### Improvements
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/flow",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.2",
|
|
4
4
|
"description": "One CLI to rule them all. Unified orchestration layer for Claude Code, OpenCode, Cursor and all AI development tools. Auto-detection, auto-installation, auto-upgrade.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-Upgrade Service
|
|
3
|
-
*
|
|
3
|
+
* Non-blocking version check with cache
|
|
4
|
+
* Checks in background, shows result on next run
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { exec } from 'node:child_process';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
7
9
|
import fs from 'node:fs/promises';
|
|
10
|
+
import os from 'node:os';
|
|
8
11
|
import path from 'node:path';
|
|
9
12
|
import { fileURLToPath } from 'node:url';
|
|
10
13
|
import { promisify } from 'node:util';
|
|
@@ -16,6 +19,16 @@ import { TargetInstaller } from './target-installer.js';
|
|
|
16
19
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
20
|
const __dirname = path.dirname(__filename);
|
|
18
21
|
|
|
22
|
+
// Cache file for version checks (24 hour TTL)
|
|
23
|
+
const CACHE_FILE = path.join(os.homedir(), '.sylphx-flow', 'version-cache.json');
|
|
24
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
25
|
+
|
|
26
|
+
interface VersionCache {
|
|
27
|
+
flowLatest?: string;
|
|
28
|
+
targetLatest?: Record<string, string>;
|
|
29
|
+
checkedAt: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
19
32
|
const execAsync = promisify(exec);
|
|
20
33
|
|
|
21
34
|
export interface UpgradeStatus {
|
|
@@ -43,80 +56,151 @@ export class AutoUpgrade {
|
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
/**
|
|
46
|
-
*
|
|
47
|
-
* @param targetId - Optional target CLI ID to check for upgrades
|
|
48
|
-
* @returns Upgrade status indicating what needs upgrading
|
|
59
|
+
* Read version cache (instant, no network)
|
|
49
60
|
*/
|
|
50
|
-
async
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
private async readCache(): Promise<VersionCache | null> {
|
|
62
|
+
try {
|
|
63
|
+
if (!existsSync(CACHE_FILE)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const data = await fs.readFile(CACHE_FILE, 'utf-8');
|
|
67
|
+
return JSON.parse(data);
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Write version cache
|
|
75
|
+
*/
|
|
76
|
+
private async writeCache(cache: VersionCache): Promise<void> {
|
|
77
|
+
try {
|
|
78
|
+
const dir = path.dirname(CACHE_FILE);
|
|
79
|
+
await fs.mkdir(dir, { recursive: true });
|
|
80
|
+
await fs.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
81
|
+
} catch {
|
|
82
|
+
// Silent fail
|
|
83
|
+
}
|
|
62
84
|
}
|
|
63
85
|
|
|
64
86
|
/**
|
|
65
|
-
*
|
|
87
|
+
* Get current Flow version from package.json (instant, local file)
|
|
66
88
|
*/
|
|
67
|
-
private async
|
|
89
|
+
private async getCurrentFlowVersion(): Promise<string> {
|
|
68
90
|
try {
|
|
69
|
-
// Get current version from package.json
|
|
70
91
|
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
|
|
71
92
|
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Get latest version from npm
|
|
75
|
-
const { stdout } = await execAsync('npm view @sylphx/flow version');
|
|
76
|
-
const latestVersion = stdout.trim();
|
|
77
|
-
|
|
78
|
-
if (currentVersion !== latestVersion) {
|
|
79
|
-
return { current: currentVersion, latest: latestVersion };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return null;
|
|
93
|
+
return packageJson.version;
|
|
83
94
|
} catch {
|
|
84
|
-
return
|
|
95
|
+
return 'unknown';
|
|
85
96
|
}
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
/**
|
|
89
|
-
* Check
|
|
100
|
+
* Check for available upgrades using CACHE (instant, no network)
|
|
101
|
+
* Returns cached results from previous background check
|
|
90
102
|
*/
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
async checkForUpgrades(targetId?: string): Promise<UpgradeStatus> {
|
|
104
|
+
const cache = await this.readCache();
|
|
105
|
+
const currentVersion = await this.getCurrentFlowVersion();
|
|
106
|
+
|
|
107
|
+
// Trigger background check for next run (non-blocking)
|
|
108
|
+
this.checkInBackground(targetId);
|
|
109
|
+
|
|
110
|
+
// No cache or expired = no upgrade info yet
|
|
111
|
+
if (!cache) {
|
|
112
|
+
return {
|
|
113
|
+
flowNeedsUpgrade: false,
|
|
114
|
+
targetNeedsUpgrade: false,
|
|
115
|
+
flowVersion: null,
|
|
116
|
+
targetVersion: null,
|
|
117
|
+
};
|
|
97
118
|
}
|
|
98
119
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
// Check if Flow needs upgrade based on cache
|
|
121
|
+
const flowVersion =
|
|
122
|
+
cache.flowLatest && cache.flowLatest !== currentVersion
|
|
123
|
+
? { current: currentVersion, latest: cache.flowLatest }
|
|
124
|
+
: null;
|
|
125
|
+
|
|
126
|
+
// Check if target needs upgrade based on cache
|
|
127
|
+
let targetVersion: { current: string; latest: string } | null = null;
|
|
128
|
+
if (targetId && cache.targetLatest?.[targetId]) {
|
|
129
|
+
const installation = this.targetInstaller.getInstallationInfo(targetId);
|
|
130
|
+
if (installation) {
|
|
131
|
+
try {
|
|
132
|
+
const { stdout } = await execAsync(installation.checkCommand);
|
|
133
|
+
const match = stdout.match(/v?(\d+\.\d+\.\d+)/);
|
|
134
|
+
if (match && match[1] !== cache.targetLatest[targetId]) {
|
|
135
|
+
targetVersion = { current: match[1], latest: cache.targetLatest[targetId] };
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// Silent
|
|
139
|
+
}
|
|
105
140
|
}
|
|
106
|
-
|
|
141
|
+
}
|
|
107
142
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
143
|
+
return {
|
|
144
|
+
flowNeedsUpgrade: !!flowVersion,
|
|
145
|
+
targetNeedsUpgrade: !!targetVersion,
|
|
146
|
+
flowVersion,
|
|
147
|
+
targetVersion,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
111
150
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Check versions in background (non-blocking)
|
|
153
|
+
* Updates cache for next run
|
|
154
|
+
*/
|
|
155
|
+
private checkInBackground(targetId?: string): void {
|
|
156
|
+
// Fire and forget - don't await
|
|
157
|
+
this.performBackgroundCheck(targetId).catch(() => {
|
|
158
|
+
// Silent fail
|
|
159
|
+
});
|
|
160
|
+
}
|
|
115
161
|
|
|
116
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Perform the actual version check (called in background)
|
|
164
|
+
*/
|
|
165
|
+
private async performBackgroundCheck(targetId?: string): Promise<void> {
|
|
166
|
+
const cache = await this.readCache();
|
|
167
|
+
|
|
168
|
+
// Skip if checked recently (within TTL)
|
|
169
|
+
if (cache && Date.now() - cache.checkedAt < CACHE_TTL_MS) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const newCache: VersionCache = {
|
|
174
|
+
checkedAt: Date.now(),
|
|
175
|
+
targetLatest: cache?.targetLatest || {},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Check Flow version from npm (with timeout)
|
|
179
|
+
try {
|
|
180
|
+
const { stdout } = await execAsync('npm view @sylphx/flow version', { timeout: 5000 });
|
|
181
|
+
newCache.flowLatest = stdout.trim();
|
|
117
182
|
} catch {
|
|
118
|
-
|
|
183
|
+
// Keep old cache value if check fails
|
|
184
|
+
newCache.flowLatest = cache?.flowLatest;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check target version from npm (with timeout)
|
|
188
|
+
if (targetId) {
|
|
189
|
+
const installation = this.targetInstaller.getInstallationInfo(targetId);
|
|
190
|
+
if (installation) {
|
|
191
|
+
try {
|
|
192
|
+
const { stdout } = await execAsync(`npm view ${installation.package} version`, {
|
|
193
|
+
timeout: 5000,
|
|
194
|
+
});
|
|
195
|
+
newCache.targetLatest = newCache.targetLatest || {};
|
|
196
|
+
newCache.targetLatest[targetId] = stdout.trim();
|
|
197
|
+
} catch {
|
|
198
|
+
// Keep old cache value
|
|
199
|
+
}
|
|
200
|
+
}
|
|
119
201
|
}
|
|
202
|
+
|
|
203
|
+
await this.writeCache(newCache);
|
|
120
204
|
}
|
|
121
205
|
|
|
122
206
|
/**
|