gloomberb 0.3.1 → 0.3.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/package.json +1 -1
- package/src/app.tsx +41 -6
- package/src/components/command-bar/command-bar.test.tsx +22 -0
- package/src/components/command-bar/command-bar.tsx +19 -1
- package/src/components/command-bar/command-registry.ts +8 -0
- package/src/components/layout/header.tsx +24 -2
- package/src/components/layout/shell.test.tsx +26 -0
- package/src/components/markdown-text.tsx +218 -0
- package/src/data/config-store.test.ts +39 -2
- package/src/data/config-store.ts +40 -2
- package/src/pane-settings.test.ts +3 -19
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +17 -5
- package/src/plugins/builtin/portfolio-list/metrics.ts +38 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +1 -11
- package/src/plugins/builtin/portfolio-metrics.test.ts +71 -0
- package/src/state/app-context.test.tsx +30 -0
- package/src/state/app-context.tsx +15 -3
- package/src/types/config.ts +12 -2
- package/src/updater.test.ts +121 -1
- package/src/updater.ts +75 -19
- package/src/version.ts +1 -1
package/src/updater.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { writeFileSync, renameSync, unlinkSync, chmodSync, realpathSync } from "fs";
|
|
2
2
|
import { basename } from "path";
|
|
3
|
+
import { gunzipSync } from "zlib";
|
|
3
4
|
|
|
4
5
|
const REPO = "vincelwt/gloomberb";
|
|
5
6
|
const API_URL = `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
@@ -10,6 +11,7 @@ export interface ReleaseInfo {
|
|
|
10
11
|
downloadUrl: string;
|
|
11
12
|
publishedAt: string;
|
|
12
13
|
updateAction: UpdateAction;
|
|
14
|
+
compressed?: boolean;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export interface UpdateProgress {
|
|
@@ -22,6 +24,12 @@ export type UpdateAction =
|
|
|
22
24
|
| { kind: "self" }
|
|
23
25
|
| { kind: "manual"; command: string };
|
|
24
26
|
|
|
27
|
+
export type UpdateCheckResult =
|
|
28
|
+
| { kind: "available"; release: ReleaseInfo }
|
|
29
|
+
| { kind: "current" }
|
|
30
|
+
| { kind: "disabled" }
|
|
31
|
+
| { kind: "error"; error: string };
|
|
32
|
+
|
|
25
33
|
function compareSemver(a: string, b: string): number {
|
|
26
34
|
const pa = a.split(".").map(Number);
|
|
27
35
|
const pb = b.split(".").map(Number);
|
|
@@ -32,13 +40,30 @@ function compareSemver(a: string, b: string): number {
|
|
|
32
40
|
return 0;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
function
|
|
43
|
+
function getAssetBaseName(): string {
|
|
36
44
|
const os = process.platform === "darwin" ? "darwin" : "linux";
|
|
37
45
|
// macOS x64 uses arm64 binary (runs via Rosetta 2)
|
|
38
46
|
const arch = os === "darwin" || process.arch === "arm64" ? "arm64" : "x64";
|
|
39
47
|
return `gloomberb-${os}-${arch}`;
|
|
40
48
|
}
|
|
41
49
|
|
|
50
|
+
function resolveReleaseAsset(
|
|
51
|
+
assets: { name: string; browser_download_url: string }[],
|
|
52
|
+
): { name: string; browser_download_url: string; compressed: boolean } | null {
|
|
53
|
+
const assetBaseName = getAssetBaseName();
|
|
54
|
+
const gzAsset = assets.find((asset) => asset.name === `${assetBaseName}.gz`);
|
|
55
|
+
if (gzAsset) {
|
|
56
|
+
return { ...gzAsset, compressed: true };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const rawAsset = assets.find((asset) => asset.name === assetBaseName);
|
|
60
|
+
if (rawAsset) {
|
|
61
|
+
return { ...rawAsset, compressed: false };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
42
67
|
function normalizePath(value: string): string {
|
|
43
68
|
return value.replace(/\\/g, "/").toLowerCase();
|
|
44
69
|
}
|
|
@@ -123,23 +148,27 @@ export function canSelfUpdate(release: Pick<ReleaseInfo, "updateAction"> | null
|
|
|
123
148
|
return release?.updateAction.kind === "self";
|
|
124
149
|
}
|
|
125
150
|
|
|
126
|
-
export async function
|
|
151
|
+
export async function checkForUpdateDetailed(
|
|
127
152
|
currentVersion: string,
|
|
128
|
-
): Promise<
|
|
153
|
+
): Promise<UpdateCheckResult> {
|
|
129
154
|
const updateAction = detectUpdateAction();
|
|
130
|
-
if (!updateAction)
|
|
155
|
+
if (!updateAction) {
|
|
156
|
+
return { kind: "disabled" };
|
|
157
|
+
}
|
|
131
158
|
|
|
159
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
132
160
|
try {
|
|
133
161
|
const controller = new AbortController();
|
|
134
|
-
|
|
162
|
+
timeout = setTimeout(() => controller.abort(), 5000);
|
|
135
163
|
|
|
136
164
|
const res = await fetch(API_URL, {
|
|
137
165
|
signal: controller.signal,
|
|
138
166
|
headers: { Accept: "application/vnd.github+json" },
|
|
139
167
|
});
|
|
140
|
-
clearTimeout(timeout);
|
|
141
168
|
|
|
142
|
-
if (!res.ok)
|
|
169
|
+
if (!res.ok) {
|
|
170
|
+
return { kind: "error", error: `GitHub returned ${res.status}` };
|
|
171
|
+
}
|
|
143
172
|
|
|
144
173
|
const data = (await res.json()) as {
|
|
145
174
|
tag_name: string;
|
|
@@ -148,24 +177,49 @@ export async function checkForUpdate(
|
|
|
148
177
|
};
|
|
149
178
|
|
|
150
179
|
const version = data.tag_name.replace(/^v/, "");
|
|
151
|
-
if (compareSemver(version, currentVersion) <= 0)
|
|
180
|
+
if (compareSemver(version, currentVersion) <= 0) {
|
|
181
|
+
return { kind: "current" };
|
|
182
|
+
}
|
|
152
183
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
184
|
+
const asset = resolveReleaseAsset(data.assets);
|
|
185
|
+
if (!asset) {
|
|
186
|
+
return {
|
|
187
|
+
kind: "error",
|
|
188
|
+
error: `No compatible release asset found for ${getAssetBaseName()}`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
156
191
|
|
|
157
192
|
return {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
193
|
+
kind: "available",
|
|
194
|
+
release: {
|
|
195
|
+
version,
|
|
196
|
+
tagName: data.tag_name,
|
|
197
|
+
downloadUrl: asset.browser_download_url,
|
|
198
|
+
publishedAt: data.published_at,
|
|
199
|
+
updateAction,
|
|
200
|
+
compressed: asset.compressed,
|
|
201
|
+
},
|
|
163
202
|
};
|
|
164
|
-
} catch {
|
|
165
|
-
|
|
203
|
+
} catch (error: unknown) {
|
|
204
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
205
|
+
return { kind: "error", error: "Update check timed out" };
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
kind: "error",
|
|
209
|
+
error: error instanceof Error ? error.message : "Update check failed",
|
|
210
|
+
};
|
|
211
|
+
} finally {
|
|
212
|
+
if (timeout) clearTimeout(timeout);
|
|
166
213
|
}
|
|
167
214
|
}
|
|
168
215
|
|
|
216
|
+
export async function checkForUpdate(
|
|
217
|
+
currentVersion: string,
|
|
218
|
+
): Promise<ReleaseInfo | null> {
|
|
219
|
+
const result = await checkForUpdateDetailed(currentVersion);
|
|
220
|
+
return result.kind === "available" ? result.release : null;
|
|
221
|
+
}
|
|
222
|
+
|
|
169
223
|
export async function performUpdate(
|
|
170
224
|
release: ReleaseInfo,
|
|
171
225
|
onProgress: (p: UpdateProgress) => void,
|
|
@@ -219,7 +273,9 @@ export async function performUpdate(
|
|
|
219
273
|
// Write to temp file
|
|
220
274
|
const blob = new Blob(chunks);
|
|
221
275
|
const buffer = await blob.arrayBuffer();
|
|
222
|
-
|
|
276
|
+
const downloaded = Buffer.from(buffer);
|
|
277
|
+
const nextBinary = release.compressed ? gunzipSync(downloaded) : downloaded;
|
|
278
|
+
writeFileSync(updatePath, nextBinary);
|
|
223
279
|
chmodSync(updatePath, 0o755);
|
|
224
280
|
|
|
225
281
|
// Swap binaries
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.3.
|
|
1
|
+
export const VERSION = "0.3.2";
|