@oh-my-pi/omp-stats 18.4.0 → 18.4.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 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.0",
4
+ "version": "18.4.2",
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.0",
46
- "@oh-my-pi/pi-catalog": "18.4.0",
47
- "@oh-my-pi/pi-utils": "18.4.0",
45
+ "@oh-my-pi/pi-ai": "18.4.2",
46
+ "@oh-my-pi/pi-catalog": "18.4.2",
47
+ "@oh-my-pi/pi-utils": "18.4.2",
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
- const EMBEDDED_CLIENT_DIR_ROOT = path.join(os.tmpdir(), "omp-stats-client");
67
- let embeddedClientDirPromise: Promise<string> | null = null;
65
+ let embeddedClientFilesPromise: Promise<Map<string, Blob>> | null = null;
68
66
 
69
- function sanitizeArchivePath(archivePath: string): string | null {
70
- const normalized = archivePath.replaceAll("\\", "/").replace(/^\.\//, "");
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
- embeddedClientDirPromise = (async () => {
103
- const bundleHash = Bun.hash(EMBEDDED_CLIENT_ARCHIVE).toString(16);
104
- const outputDir = path.join(EMBEDDED_CLIENT_DIR_ROOT, bundleHash);
105
- const markerPath = path.join(outputDir, "index.html");
106
- try {
107
- const marker = await fs.stat(markerPath);
108
- if (marker.isFile()) return outputDir;
109
- } catch {}
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 embeddedClientDirPromise;
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
- const staticDir = await getEmbeddedClientDir();
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(staticDir, filePath);
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(staticDir, "index.html"));
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
  }