@vertz/ui-server 0.2.10 → 0.2.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.
@@ -40,6 +40,28 @@ interface ErrorDetail {
40
40
  stack?: string;
41
41
  }
42
42
  type ErrorCategory = "build" | "resolve" | "runtime" | "ssr";
43
+ /** A resolved stack frame for terminal logging. */
44
+ interface TerminalStackFrame {
45
+ functionName: string | null;
46
+ file: string;
47
+ line: number;
48
+ column: number;
49
+ }
50
+ /**
51
+ * Format a runtime error for terminal output.
52
+ * Produces a [Browser]-prefixed message with optional file location,
53
+ * line text snippet, and resolved stack frames.
54
+ */
55
+ declare function formatTerminalRuntimeError(errors: ErrorDetail[], parsedStack?: TerminalStackFrame[]): string;
56
+ /**
57
+ * Create a deduplicator for terminal runtime error logs.
58
+ * Returns `shouldLog` (true if this error hasn't been logged recently)
59
+ * and `reset` (to clear on file change).
60
+ */
61
+ declare function createRuntimeErrorDeduplicator(): {
62
+ shouldLog: (message: string, file?: string, line?: number) => boolean;
63
+ reset: () => void;
64
+ };
43
65
  interface BunDevServer {
44
66
  start(): Promise<void>;
45
67
  stop(): Promise<void>;
@@ -53,15 +75,6 @@ interface BunDevServer {
53
75
  /** Set the last changed file path (for testing). */
54
76
  setLastChangedFile(file: string): void;
55
77
  }
56
- interface IndexHtmlStasher {
57
- stash(): void;
58
- restore(): void;
59
- }
60
- /**
61
- * Create a stasher that renames index.html during dev so Bun's built-in
62
- * HMR server doesn't auto-serve it, bypassing our SSR fetch handler.
63
- */
64
- declare function createIndexHtmlStasher(projectRoot: string): IndexHtmlStasher;
65
78
  interface HMRAssets {
66
79
  /** Discovered `/_bun/client/<hash>.js` URL, or null if not found */
67
80
  scriptUrl: string | null;
@@ -119,4 +132,4 @@ declare function buildScriptTag(bundledScriptUrl: string | null, hmrBootstrapScr
119
132
  * SSR is always on. HMR always works. No mode toggle needed.
120
133
  */
121
134
  declare function createBunDevServer(options: BunDevServerOptions): BunDevServer;
122
- export { parseHMRAssets, generateSSRPageHtml, createIndexHtmlStasher, createFetchInterceptor, createBunDevServer, buildScriptTag, SSRPageHtmlOptions, IndexHtmlStasher, HMRAssets, FetchInterceptorOptions, ErrorDetail, ErrorCategory, BunDevServerOptions, BunDevServer };
135
+ export { parseHMRAssets, generateSSRPageHtml, formatTerminalRuntimeError, createRuntimeErrorDeduplicator, createFetchInterceptor, createBunDevServer, buildScriptTag, SSRPageHtmlOptions, HMRAssets, FetchInterceptorOptions, ErrorDetail, ErrorCategory, BunDevServerOptions, BunDevServer };
@@ -5,7 +5,7 @@ import {
5
5
 
6
6
  // src/bun-dev-server.ts
7
7
  import { execSync } from "child_process";
8
- import { existsSync, mkdirSync, readFileSync as readFileSync2, renameSync, watch, writeFileSync as writeFileSync2 } from "fs";
8
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, watch, writeFileSync as writeFileSync2 } from "fs";
9
9
  import { dirname, normalize, resolve } from "path";
10
10
 
11
11
  // src/debug-logger.ts
@@ -62,6 +62,8 @@ class DiagnosticsCollector {
62
62
  wsConnectedClients = 0;
63
63
  watcherLastChangedFile = null;
64
64
  watcherLastChangeTime = null;
65
+ static MAX_RUNTIME_ERRORS = 10;
66
+ runtimeErrorsBuffer = [];
65
67
  recordPluginConfig(filter, hmr, fastRefresh) {
66
68
  this.pluginFilter = filter;
67
69
  this.pluginHmr = hmr;
@@ -103,6 +105,19 @@ class DiagnosticsCollector {
103
105
  this.watcherLastChangedFile = file;
104
106
  this.watcherLastChangeTime = new Date().toISOString();
105
107
  }
108
+ recordRuntimeError(message, source) {
109
+ this.runtimeErrorsBuffer.push({
110
+ message,
111
+ source,
112
+ timestamp: new Date().toISOString()
113
+ });
114
+ if (this.runtimeErrorsBuffer.length > DiagnosticsCollector.MAX_RUNTIME_ERRORS) {
115
+ this.runtimeErrorsBuffer = this.runtimeErrorsBuffer.slice(this.runtimeErrorsBuffer.length - DiagnosticsCollector.MAX_RUNTIME_ERRORS);
116
+ }
117
+ }
118
+ clearRuntimeErrors() {
119
+ this.runtimeErrorsBuffer = [];
120
+ }
106
121
  getSnapshot() {
107
122
  return {
108
123
  status: "ok",
@@ -137,7 +152,8 @@ class DiagnosticsCollector {
137
152
  watcher: {
138
153
  lastChangedFile: this.watcherLastChangedFile,
139
154
  lastChangeTime: this.watcherLastChangeTime
140
- }
155
+ },
156
+ runtimeErrors: [...this.runtimeErrorsBuffer]
141
157
  };
142
158
  }
143
159
  }
@@ -1199,6 +1215,45 @@ data: ${safeSerialize(entry)}
1199
1215
  }
1200
1216
 
1201
1217
  // src/bun-dev-server.ts
1218
+ var MAX_TERMINAL_STACK_FRAMES = 5;
1219
+ function formatTerminalRuntimeError(errors, parsedStack) {
1220
+ const primary = errors[0];
1221
+ if (!primary)
1222
+ return "";
1223
+ const lines = [];
1224
+ lines.push(`[Browser] ${primary.message}`);
1225
+ if (primary.file) {
1226
+ const loc = primary.line ? `${primary.file}:${primary.line}${primary.column != null ? `:${primary.column}` : ""}` : primary.file;
1227
+ lines.push(` at ${loc}`);
1228
+ }
1229
+ if (primary.lineText) {
1230
+ lines.push(` \u2502 ${primary.lineText}`);
1231
+ }
1232
+ if (parsedStack?.length) {
1233
+ const frames = parsedStack.slice(0, MAX_TERMINAL_STACK_FRAMES);
1234
+ for (const frame of frames) {
1235
+ const fn = frame.functionName ? `${frame.functionName} ` : "";
1236
+ lines.push(` at ${fn}${frame.file}:${frame.line}:${frame.column}`);
1237
+ }
1238
+ }
1239
+ return lines.join(`
1240
+ `);
1241
+ }
1242
+ function createRuntimeErrorDeduplicator() {
1243
+ let lastKey = "";
1244
+ return {
1245
+ shouldLog(message, file, line) {
1246
+ const key = `${message}::${file ?? ""}::${line ?? ""}`;
1247
+ if (key === lastKey)
1248
+ return false;
1249
+ lastKey = key;
1250
+ return true;
1251
+ },
1252
+ reset() {
1253
+ lastKey = "";
1254
+ }
1255
+ };
1256
+ }
1202
1257
  function killStaleProcess(targetPort) {
1203
1258
  try {
1204
1259
  const output = execSync(`lsof -ti :${targetPort}`, { encoding: "utf8" }).trim();
@@ -1217,29 +1272,6 @@ function killStaleProcess(targetPort) {
1217
1272
  }
1218
1273
  } catch {}
1219
1274
  }
1220
- function createIndexHtmlStasher(projectRoot) {
1221
- const indexHtmlPath = resolve(projectRoot, "index.html");
1222
- const indexHtmlBackupPath = resolve(projectRoot, ".vertz", "dev", "index.html.bak");
1223
- let stashed = false;
1224
- return {
1225
- stash() {
1226
- if (!existsSync(indexHtmlPath) && existsSync(indexHtmlBackupPath)) {
1227
- renameSync(indexHtmlBackupPath, indexHtmlPath);
1228
- }
1229
- if (existsSync(indexHtmlPath)) {
1230
- mkdirSync(resolve(projectRoot, ".vertz", "dev"), { recursive: true });
1231
- renameSync(indexHtmlPath, indexHtmlBackupPath);
1232
- stashed = true;
1233
- }
1234
- },
1235
- restore() {
1236
- if (stashed && existsSync(indexHtmlBackupPath)) {
1237
- renameSync(indexHtmlBackupPath, indexHtmlPath);
1238
- stashed = false;
1239
- }
1240
- }
1241
- };
1242
- }
1243
1275
  function parseHMRAssets(html) {
1244
1276
  const srcMatch = html.match(/src="(\/_bun\/client\/[^"]+\.js)"/);
1245
1277
  const bootstrapMatch = html.match(/<script>(\(\(a\)=>\{document\.addEventListener.*?)<\/script>/);
@@ -1585,6 +1617,7 @@ function createBunDevServer(options) {
1585
1617
  let lastBuildError = "";
1586
1618
  let lastBroadcastedError = "";
1587
1619
  let lastChangedFile = "";
1620
+ const terminalDedup = createRuntimeErrorDeduplicator();
1588
1621
  const resolvePatterns = ["Could not resolve", "Module not found", "Cannot find module"];
1589
1622
  const hmrErrorPattern = /\[vertz-hmr\] Error re-mounting (\w+): ([\s\S]*?)(?:\n\s+at |$)/;
1590
1623
  const frontendErrorPattern = /\x1b\[31mfrontend\x1b\[0m ([\s\S]*?)(?:\n\s+from browser|$)/;
@@ -1611,6 +1644,19 @@ function createBunDevServer(options) {
1611
1644
  return { message: "" };
1612
1645
  }
1613
1646
  const origConsoleError = console.error;
1647
+ function logRuntimeErrorToTerminal(errors, parsedStack) {
1648
+ const primary = errors[0];
1649
+ if (!primary)
1650
+ return;
1651
+ if (!terminalDedup.shouldLog(primary.message, primary.file, primary.line))
1652
+ return;
1653
+ const formatted = formatTerminalRuntimeError(errors, parsedStack);
1654
+ if (formatted) {
1655
+ origConsoleError(formatted);
1656
+ }
1657
+ diagnostics.recordRuntimeError(primary.message, primary.file ?? null);
1658
+ diagnostics.recordError("runtime", primary.message);
1659
+ }
1614
1660
  console.error = (...args) => {
1615
1661
  const text = args.map((a) => typeof a === "string" ? a : String(a)).join(" ");
1616
1662
  if (!text.startsWith("[Server]")) {
@@ -1666,7 +1712,6 @@ function createBunDevServer(options) {
1666
1712
  }
1667
1713
  origConsoleError.apply(console, args);
1668
1714
  };
1669
- const indexHtmlStasher = createIndexHtmlStasher(projectRoot);
1670
1715
  let cachedSpec = null;
1671
1716
  let specWatcher = null;
1672
1717
  const loadOpenAPISpec = () => {
@@ -1718,7 +1763,6 @@ function createBunDevServer(options) {
1718
1763
  return new Response("OpenAPI spec not found", { status: 404 });
1719
1764
  };
1720
1765
  async function start() {
1721
- indexHtmlStasher.stash();
1722
1766
  const { plugin } = await Promise.resolve(globalThis.Bun);
1723
1767
  const { createVertzBunPlugin } = await import("./bun-plugin/index.js");
1724
1768
  const entryPath = resolve(projectRoot, entry);
@@ -2001,6 +2045,7 @@ data: {}
2001
2045
  parsedStack: result.parsedStack
2002
2046
  };
2003
2047
  currentError = { category: "runtime", errors: enrichedErrors };
2048
+ logRuntimeErrorToTerminal(enrichedErrors, result.parsedStack);
2004
2049
  const text2 = JSON.stringify(payload2);
2005
2050
  for (const client of wsClients) {
2006
2051
  client.sendText(text2);
@@ -2015,6 +2060,7 @@ data: {}
2015
2060
  parsedStack: result.parsedStack
2016
2061
  };
2017
2062
  currentError = { category: "runtime", errors: result.errors };
2063
+ logRuntimeErrorToTerminal(result.errors, result.parsedStack);
2018
2064
  const text = JSON.stringify(payload);
2019
2065
  for (const client of wsClients) {
2020
2066
  client.sendText(text);
@@ -2036,12 +2082,17 @@ data: {}
2036
2082
  errors
2037
2083
  };
2038
2084
  currentError = { category: "runtime", errors };
2085
+ logRuntimeErrorToTerminal(errors);
2039
2086
  const text = JSON.stringify(payload);
2040
2087
  for (const client of wsClients) {
2041
2088
  client.sendText(text);
2042
2089
  }
2043
2090
  } else {
2044
- broadcastError("runtime", [{ message: data.message ?? "Unknown error" }]);
2091
+ const fallbackErrors = [
2092
+ { message: data.message ?? "Unknown error" }
2093
+ ];
2094
+ logRuntimeErrorToTerminal(fallbackErrors);
2095
+ broadcastError("runtime", fallbackErrors);
2045
2096
  }
2046
2097
  });
2047
2098
  }
@@ -2096,6 +2147,8 @@ data: {}
2096
2147
  diagnostics.recordFileChange(lastChangedFile);
2097
2148
  logger.log("watcher", "file-changed", { file: lastChangedFile });
2098
2149
  lastBroadcastedError = "";
2150
+ terminalDedup.reset();
2151
+ diagnostics.clearRuntimeErrors();
2099
2152
  sourceMapResolver.invalidate();
2100
2153
  if (logRequests) {
2101
2154
  console.log(`[Server] File changed: ${filename}`);
@@ -2233,14 +2286,14 @@ data: {}
2233
2286
  server.stop(true);
2234
2287
  server = null;
2235
2288
  }
2236
- indexHtmlStasher.restore();
2237
2289
  }
2238
2290
  };
2239
2291
  }
2240
2292
  export {
2241
2293
  parseHMRAssets,
2242
2294
  generateSSRPageHtml,
2243
- createIndexHtmlStasher,
2295
+ formatTerminalRuntimeError,
2296
+ createRuntimeErrorDeduplicator,
2244
2297
  createFetchInterceptor,
2245
2298
  createBunDevServer,
2246
2299
  buildScriptTag
@@ -23,6 +23,7 @@ var DIRTY_KEY = Symbol.for("vertz:fast-refresh:dirty");
23
23
  var registry = globalThis[REGISTRY_KEY] ??= new Map;
24
24
  var dirtyModules = globalThis[DIRTY_KEY] ??= new Set;
25
25
  var performingRefresh = false;
26
+ var refreshSignals = null;
26
27
  function getModule(moduleId) {
27
28
  let mod = registry.get(moduleId);
28
29
  if (!mod) {
@@ -42,8 +43,10 @@ function __$refreshReg(moduleId, name, factory) {
42
43
  }
43
44
  }
44
45
  function __$refreshTrack(moduleId, name, element, args, cleanups, contextScope, signals = []) {
45
- if (performingRefresh)
46
+ if (performingRefresh) {
47
+ refreshSignals = signals;
46
48
  return element;
49
+ }
47
50
  const mod = registry.get(moduleId);
48
51
  if (!mod)
49
52
  return element;
@@ -76,12 +79,13 @@ function __$refreshPerform(moduleId) {
76
79
  let newSignals;
77
80
  let newContextScope;
78
81
  try {
79
- startSignalCollection();
82
+ refreshSignals = null;
80
83
  newElement = factory(...args);
81
- newSignals = stopSignalCollection();
84
+ newSignals = refreshSignals ?? [];
85
+ refreshSignals = null;
82
86
  newContextScope = getContextScope();
83
87
  } catch (err) {
84
- stopSignalCollection();
88
+ refreshSignals = null;
85
89
  runCleanups(newCleanups);
86
90
  popScope();
87
91
  setContextScope(prevScope);
@@ -5,6 +5,11 @@ interface DebugLogger {
5
5
  log(category: DebugCategory, message: string, data?: Record<string, unknown>): void;
6
6
  isEnabled(category: DebugCategory): boolean;
7
7
  }
8
+ interface RuntimeErrorEntry {
9
+ message: string;
10
+ source: string | null;
11
+ timestamp: string;
12
+ }
8
13
  interface DiagnosticsSnapshot {
9
14
  status: "ok";
10
15
  uptime: number;
@@ -39,6 +44,7 @@ interface DiagnosticsSnapshot {
39
44
  lastChangedFile: string | null;
40
45
  lastChangeTime: string | null;
41
46
  };
47
+ runtimeErrors: RuntimeErrorEntry[];
42
48
  }
43
49
  declare class DiagnosticsCollector {
44
50
  private startTime;
@@ -61,6 +67,8 @@ declare class DiagnosticsCollector {
61
67
  private wsConnectedClients;
62
68
  private watcherLastChangedFile;
63
69
  private watcherLastChangeTime;
70
+ private static readonly MAX_RUNTIME_ERRORS;
71
+ private runtimeErrorsBuffer;
64
72
  recordPluginConfig(filter: string, hmr: boolean, fastRefresh: boolean): void;
65
73
  recordPluginProcess(file: string): void;
66
74
  recordSSRReload(success: boolean, durationMs: number, error?: string): void;
@@ -69,6 +77,8 @@ declare class DiagnosticsCollector {
69
77
  recordErrorClear(): void;
70
78
  recordWebSocketChange(count: number): void;
71
79
  recordFileChange(file: string): void;
80
+ recordRuntimeError(message: string, source: string | null): void;
81
+ clearRuntimeErrors(): void;
72
82
  getSnapshot(): DiagnosticsSnapshot;
73
83
  }
74
84
  import { BunPlugin as BunPlugin_seob6 } from "bun";
@@ -165,9 +165,6 @@ function createVertzBunPlugin(options) {
165
165
  refreshEpilogue = refreshCode.epilogue;
166
166
  }
167
167
  }
168
- const mapBase64 = Buffer.from(remapped.toString()).toString("base64");
169
- const sourceMapComment = `
170
- //# sourceMappingURL=data:application/json;base64,${mapBase64}`;
171
168
  let contents = "";
172
169
  if (cssImportLine) {
173
170
  contents += cssImportLine;
@@ -175,6 +172,14 @@ function createVertzBunPlugin(options) {
175
172
  if (refreshPreamble) {
176
173
  contents += refreshPreamble;
177
174
  }
175
+ const prependedLines = contents.split(`
176
+ `).length - 1;
177
+ if (prependedLines > 0) {
178
+ remapped.mappings = ";".repeat(prependedLines) + remapped.mappings;
179
+ }
180
+ const mapBase64 = Buffer.from(remapped.toString()).toString("base64");
181
+ const sourceMapComment = `
182
+ //# sourceMappingURL=data:application/json;base64,${mapBase64}`;
178
183
  contents += compileResult.code;
179
184
  if (refreshEpilogue) {
180
185
  contents += refreshEpilogue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/ui-server",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz UI server-side rendering runtime",
@@ -53,9 +53,9 @@
53
53
  "dependencies": {
54
54
  "@ampproject/remapping": "^2.3.0",
55
55
  "@jridgewell/trace-mapping": "^0.3.31",
56
- "@vertz/core": "^0.2.4",
57
- "@vertz/ui": "^0.2.2",
58
- "@vertz/ui-compiler": "^0.2.4",
56
+ "@vertz/core": "^0.2.11",
57
+ "@vertz/ui": "^0.2.11",
58
+ "@vertz/ui-compiler": "^0.2.11",
59
59
  "magic-string": "^0.30.0",
60
60
  "ts-morph": "^27.0.2"
61
61
  },