Skip to content

Commit a0b5109

Browse files
committed
Fix require calls in markdown stack due to upgrading dep to ESM-only
1 parent b91b40d commit a0b5109

8 files changed

Lines changed: 196 additions & 248 deletions

File tree

‎SESSION.md‎

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

‎astro.config.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,26 @@ export default defineConfig({
188188
*/
189189
include: ['lit', 'lit/directives/if-defined.js'],
190190
},
191+
ssr: {
192+
/**
193+
* sanitize-html (CommonJS) loads its HTML parser with require(), but its
194+
* dependency chain (htmlparser2 v10+, dom-serializer v3, domhandler v6,
195+
* domutils v4, domelementtype v3, entities v8) is ESM-only. Vercel's
196+
* serverless module loader cannot require() ES modules, which crashed
197+
* POST /_actions/webmentions.list with ERR_REQUIRE_ESM in production.
198+
* Bundle the sanitizer and its parser dependency chain into the ESM
199+
* server output so no runtime require() of these packages occurs.
200+
*/
201+
noExternal: [
202+
'sanitize-html',
203+
'htmlparser2',
204+
'dom-serializer',
205+
'domelementtype',
206+
'domhandler',
207+
'domutils',
208+
'entities',
209+
],
210+
},
191211
/**
192212
* Astro 6 reads `environments.client.build.sourcemap` for client bundles
193213
* instead of the top-level `build.sourcemap` (which only affects server).

‎src/components/WebMentions/client/__tests__/index.spec.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,33 @@ describe('WebMentions web component', () => {
225225
{ url: 'https://example.com/forbidden-thrown-post' }
226226
)
227227
})
228+
229+
test('reports empty action error messages with descriptive fallback details', async () => {
230+
/** Mirrors the ActionError Astro returns for a 500 with a non-JSON body. */
231+
const emptyActionError = Object.assign(new Error(''), {
232+
code: 'INTERNAL_SERVER_ERROR',
233+
status: 500,
234+
})
235+
webmentionsListMock.mockResolvedValue({
236+
data: undefined,
237+
error: emptyActionError,
238+
})
239+
240+
await runComponentRender(
241+
async ({ element }) => {
242+
await flushMicrotasks()
243+
await element.updateComplete
244+
245+
expect(element.querySelector('#webmentions')).toBeNull()
246+
expect(handleScriptErrorMock).toHaveBeenCalledWith(
247+
expect.objectContaining({
248+
message: 'Failed to load WebMentions data. (INTERNAL_SERVER_ERROR, HTTP status 500)',
249+
cause: emptyActionError,
250+
}),
251+
{ scriptName: 'WebMentionsElement', operation: 'load' }
252+
)
253+
},
254+
{ url: 'https://example.com/empty-message-post' }
255+
)
256+
})
228257
})

‎src/components/WebMentions/client/index.ts‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { handleScriptError } from '@components/scripts/errors/handler'
66
import {
77
isForbiddenClientActionError,
88
normalizeClientActionError,
9+
type ClientActionError,
910
} from '@components/scripts/errors/actionClient'
1011
import type { WebComponentModule } from '@components/scripts/@types/webComponentModule'
1112
import type { WebmentionDisplayItem, WebmentionsListResult } from '@actions/webmentions/@types'
@@ -21,6 +22,28 @@ const emptyWebmentionsResult: WebmentionsListResult = {
2122
repostsCount: 0,
2223
}
2324

25+
/**
26+
* Astro's action client surfaces unexpected server failures (such as an HTTP 500
27+
* with a non-JSON body) as an ActionError with an empty message, which Sentry
28+
* would record as "Unknown error". Build a descriptive error from the normalized
29+
* action error so handled reports stay diagnosable, keeping the original as cause.
30+
*/
31+
const toLoadReportError = (error: unknown, actionError?: ClientActionError): unknown => {
32+
if (error instanceof Error && error.message.trim().length > 0) {
33+
return error
34+
}
35+
36+
const details = [
37+
actionError?.code,
38+
actionError?.status ? `HTTP status ${actionError.status}` : undefined,
39+
]
40+
.filter(Boolean)
41+
.join(', ')
42+
const message = `Failed to load WebMentions data.${details ? ` (${details})` : ''}`
43+
44+
return new Error(message, error instanceof Error ? { cause: error } : undefined)
45+
}
46+
2447
const formatDate = (dateString: string): string => {
2548
const date = new Date(dateString)
2649
return new Intl.DateTimeFormat('en-US', {
@@ -126,7 +149,7 @@ export class WebMentionsElement extends LitElement {
126149
return
127150
}
128151

129-
handleScriptError(error ?? new Error('Failed to load WebMentions data.'), {
152+
handleScriptError(toLoadReportError(error, actionError), {
130153
scriptName,
131154
operation: 'load',
132155
})
@@ -150,7 +173,7 @@ export class WebMentionsElement extends LitElement {
150173
return
151174
}
152175

153-
handleScriptError(error, { scriptName, operation: 'load' })
176+
handleScriptError(toLoadReportError(error, actionError), { scriptName, operation: 'load' })
154177

155178
if (this.lastLoadedUrl === normalizedUrl) {
156179
this.lastLoadedUrl = ''

0 commit comments

Comments
 (0)