Skip to content

Commit 18ea780

Browse files
committed
fix spamming errors in log and markpdf cli issues
1 parent a16946c commit 18ea780

6 files changed

Lines changed: 209 additions & 257 deletions

File tree

artifacts/artifacts/api-server/src/lib/renderer.test.ts

Lines changed: 0 additions & 175 deletions
This file was deleted.

artifacts/artifacts/api-server/src/lib/renderer.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,54 @@ function normalizeMarkdownImageTarget(rawTarget: string): string {
109109
return trimmed;
110110
}
111111

112-
function normalizeAssetReference(rawRef: string): string | null {
112+
function normalizeAssetPathLikeReference(rawRef: string): string {
113113
const trimmed = rawRef.trim();
114-
if (!trimmed) return null;
114+
if (!trimmed) return "";
115115

116116
const noQuotes = trimmed.replace(/^["']|["']$/g, "");
117117
const noAngles = noQuotes.replace(/^<|>$/g, "");
118118
const noQueryOrHash = noAngles.split(/[?#]/, 1)[0]?.trim() ?? "";
119119
const normalizedSlashes = noQueryOrHash.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
120-
const normalizedLeading = normalizedSlashes.replace(/^\.\//, "").replace(/^\//, "");
120+
return normalizedSlashes.replace(/^\.\//, "").replace(/^\//, "");
121+
}
122+
123+
function isDirectoryAssetReference(path: string): boolean {
124+
return path === "assets" || path === "assets/" || (path.startsWith("assets/") && path.endsWith("/"));
125+
}
126+
127+
function normalizeAssetReference(rawRef: string): string | null {
128+
const normalizedLeading = normalizeAssetPathLikeReference(rawRef);
121129
if (!normalizedLeading.startsWith("assets/")) return null;
130+
if (isDirectoryAssetReference(normalizedLeading)) return null;
122131

123132
return normalizedLeading;
124133
}
125134

135+
export function extractInvalidDirectoryAssetReferences(markdown: string): string[] {
136+
const invalidRefs = new Set<string>();
137+
markdownImageRefRegex.lastIndex = 0;
138+
typstImageRefRegex.lastIndex = 0;
139+
140+
let match: RegExpExecArray | null;
141+
while ((match = markdownImageRefRegex.exec(markdown)) !== null) {
142+
const candidate = match[1] ? normalizeMarkdownImageTarget(match[1]) : (match[2] ?? "");
143+
const normalized = normalizeAssetPathLikeReference(candidate);
144+
if (isDirectoryAssetReference(normalized)) {
145+
invalidRefs.add(normalized);
146+
}
147+
}
148+
149+
while ((match = typstImageRefRegex.exec(markdown)) !== null) {
150+
const candidate = match[1] ?? match[2] ?? "";
151+
const normalized = normalizeAssetPathLikeReference(candidate);
152+
if (isDirectoryAssetReference(normalized)) {
153+
invalidRefs.add(normalized);
154+
}
155+
}
156+
157+
return Array.from(invalidRefs).sort();
158+
}
159+
126160
export function extractReferencedAssetPaths(markdown: string): string[] {
127161
const paths = new Set<string>();
128162
markdownImageRefRegex.lastIndex = 0;
@@ -631,6 +665,13 @@ export async function renderMarkdownToPdf(
631665

632666
const options: Required<RenderOptions> = { ...DEFAULT_RENDER_OPTIONS, ...(rawOptions ?? {}) };
633667
const normalizedMarkdown = normalizeMarkdownForPdf(markdown);
668+
const invalidDirectoryReferences = extractInvalidDirectoryAssetReferences(normalizedMarkdown);
669+
if (invalidDirectoryReferences.length > 0) {
670+
const sample = invalidDirectoryReferences.slice(0, 4).join(", ");
671+
throw new Error(
672+
`Invalid image path points to an assets directory: ${sample}. Use a file path under assets/ (for example assets/image.png).`,
673+
);
674+
}
634675

635676
await ensureTmpDir();
636677
const id = randomBytes(8).toString("hex");

artifacts/artifacts/api-server/src/lib/websocket.test.ts

Lines changed: 0 additions & 49 deletions
This file was deleted.

artifacts/artifacts/api-server/src/lib/websocket.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export function setupWebSocket(server: Server) {
100100
let activeRenderAbortController: AbortController | null = null;
101101
let lastRequestedKey = "";
102102
let lastRenderedKey = "";
103+
let lastBroadcastErrorKey = "";
103104

104105
async function doRender(projectId: number, content: string, options: RenderOptions, requestedAtMs: number) {
105106
const key = `${projectId}:${content}:${JSON.stringify(options)}`;
@@ -129,6 +130,7 @@ export function setupWebSocket(server: Server) {
129130
}
130131

131132
lastRenderedKey = key;
133+
lastBroadcastErrorKey = "";
132134
if (ws.readyState === WebSocket.OPEN) {
133135
ws.send(pdfBytes, { binary: true });
134136
}
@@ -153,9 +155,19 @@ export function setupWebSocket(server: Server) {
153155
return;
154156
}
155157

158+
const errorMessage = (err as Error)?.message || "Preview render failed";
159+
const errorKey = `${key}:${errorMessage}`;
160+
if (errorKey === lastBroadcastErrorKey) {
161+
if (config.metricsEnabled) {
162+
logger.debug({ key }, "Skipping duplicate WebSocket render error for identical content");
163+
}
164+
return;
165+
}
166+
167+
lastBroadcastErrorKey = errorKey;
156168
logger.error({ err }, "WebSocket render failed");
157169
if (ws.readyState === WebSocket.OPEN) {
158-
ws.send(JSON.stringify({ error: (err as Error).message }));
170+
ws.send(JSON.stringify({ error: errorMessage }));
159171
}
160172
} finally {
161173
if (activeRenderAbortController === renderAbortController) {

0 commit comments

Comments
 (0)