@oh-my-pi/omp-stats 18.4.0 → 18.4.1
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 +4 -4
- package/src/server.ts +20 -47
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.4.1] - 2026-09-28
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed `/trace` and `omp stats` dashboards failing to load after operating-system temporary-file cleanup ([#13487](https://github.com/can1357/oh-my-pi/pull/13487) by [@Peter-Tam](https://github.com/Peter-Tam)).
|
|
10
|
+
|
|
5
11
|
## [18.4.0] - 2026-09-28
|
|
6
12
|
|
|
7
13
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/omp-stats",
|
|
4
|
-
"version": "18.4.
|
|
4
|
+
"version": "18.4.1",
|
|
5
5
|
"description": "Local observability dashboard for omp AI usage statistics",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@oh-my-pi/pi-ai": "18.4.
|
|
46
|
-
"@oh-my-pi/pi-catalog": "18.4.
|
|
47
|
-
"@oh-my-pi/pi-utils": "18.4.
|
|
45
|
+
"@oh-my-pi/pi-ai": "18.4.1",
|
|
46
|
+
"@oh-my-pi/pi-catalog": "18.4.1",
|
|
47
|
+
"@oh-my-pi/pi-utils": "18.4.1",
|
|
48
48
|
"lucide-react": "^1.24.0",
|
|
49
49
|
"react": "19.2.7",
|
|
50
50
|
"react-dom": "19.2.7"
|
package/src/server.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Dirent } from "node:fs";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
|
-
import * as os from "node:os";
|
|
4
3
|
import * as path from "node:path";
|
|
5
4
|
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
6
5
|
import { $, type Server } from "bun";
|
|
@@ -63,35 +62,10 @@ const IS_BUN_COMPILED =
|
|
|
63
62
|
const IS_PREBUILT = IS_BUN_COMPILED || Boolean(process.env.PI_BUNDLED || Bun.env.PI_BUNDLED);
|
|
64
63
|
const USE_EMBEDDED_CLIENT = EMBEDDED_CLIENT_ARCHIVE !== null || IS_PREBUILT;
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
let embeddedClientDirPromise: Promise<string> | null = null;
|
|
65
|
+
let embeddedClientFilesPromise: Promise<Map<string, Blob>> | null = null;
|
|
68
66
|
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
if (!normalized || normalized === ".") return null;
|
|
72
|
-
if (normalized.includes("..") || path.isAbsolute(normalized)) return null;
|
|
73
|
-
return normalized;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function extractEmbeddedClientArchive(archiveBytes: Buffer, outputDir: string): Promise<void> {
|
|
77
|
-
const archive = new Bun.Archive(archiveBytes);
|
|
78
|
-
const files = await archive.files();
|
|
79
|
-
const extractRoot = path.resolve(outputDir);
|
|
80
|
-
|
|
81
|
-
for (const [archivePath, file] of files) {
|
|
82
|
-
const sanitizedPath = sanitizeArchivePath(archivePath);
|
|
83
|
-
if (!sanitizedPath) continue;
|
|
84
|
-
const destinationPath = path.resolve(extractRoot, sanitizedPath);
|
|
85
|
-
if (!destinationPath.startsWith(extractRoot + path.sep)) {
|
|
86
|
-
throw new Error(`Archive entry escapes extraction directory: ${archivePath}`);
|
|
87
|
-
}
|
|
88
|
-
await Bun.write(destinationPath, file);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function getEmbeddedClientDir(): Promise<string> {
|
|
93
|
-
if (!USE_EMBEDDED_CLIENT) return STATIC_DIR;
|
|
94
|
-
if (embeddedClientDirPromise) return embeddedClientDirPromise;
|
|
67
|
+
async function getEmbeddedClientFiles(): Promise<Map<string, Blob>> {
|
|
68
|
+
if (embeddedClientFilesPromise) return embeddedClientFilesPromise;
|
|
95
69
|
|
|
96
70
|
if (!EMBEDDED_CLIENT_ARCHIVE) {
|
|
97
71
|
throw new Error(
|
|
@@ -99,22 +73,16 @@ async function getEmbeddedClientDir(): Promise<string> {
|
|
|
99
73
|
);
|
|
100
74
|
}
|
|
101
75
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
await fs.rm(outputDir, { recursive: true, force: true });
|
|
112
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
113
|
-
await extractEmbeddedClientArchive(EMBEDDED_CLIENT_ARCHIVE, outputDir);
|
|
114
|
-
return outputDir;
|
|
115
|
-
})();
|
|
76
|
+
// Keep bundled assets in memory so OS temporary-file cleanup cannot break a live dashboard.
|
|
77
|
+
embeddedClientFilesPromise = new Bun.Archive(EMBEDDED_CLIENT_ARCHIVE).files().then(files => {
|
|
78
|
+
for (const [name, file] of files) {
|
|
79
|
+
// Archive entries are untyped blobs; infer MIME types just as disk-backed Bun files do.
|
|
80
|
+
files.set(name, new File([file], name, { type: Bun.file(name).type }));
|
|
81
|
+
}
|
|
82
|
+
return files;
|
|
83
|
+
});
|
|
116
84
|
|
|
117
|
-
return
|
|
85
|
+
return embeddedClientFilesPromise;
|
|
118
86
|
}
|
|
119
87
|
|
|
120
88
|
async function getLatestMtime(dir: string): Promise<number> {
|
|
@@ -364,9 +332,14 @@ export async function handleApi(req: Request): Promise<Response> {
|
|
|
364
332
|
* Handle static file requests.
|
|
365
333
|
*/
|
|
366
334
|
async function handleStatic(requestPath: string): Promise<Response> {
|
|
367
|
-
|
|
335
|
+
if (USE_EMBEDDED_CLIENT) {
|
|
336
|
+
const files = await getEmbeddedClientFiles();
|
|
337
|
+
const file = files.get(requestPath.slice(1)) ?? files.get("index.html");
|
|
338
|
+
return file ? new Response(file) : new Response("Not Found", { status: 404 });
|
|
339
|
+
}
|
|
340
|
+
|
|
368
341
|
const filePath = requestPath === "/" ? "/index.html" : requestPath;
|
|
369
|
-
const fullPath = path.join(
|
|
342
|
+
const fullPath = path.join(STATIC_DIR, filePath);
|
|
370
343
|
|
|
371
344
|
const file = Bun.file(fullPath);
|
|
372
345
|
if (await file.exists()) {
|
|
@@ -374,7 +347,7 @@ async function handleStatic(requestPath: string): Promise<Response> {
|
|
|
374
347
|
}
|
|
375
348
|
|
|
376
349
|
// SPA fallback
|
|
377
|
-
const index = Bun.file(path.join(
|
|
350
|
+
const index = Bun.file(path.join(STATIC_DIR, "index.html"));
|
|
378
351
|
if (await index.exists()) {
|
|
379
352
|
return new Response(index);
|
|
380
353
|
}
|