@barefootjs/hono 0.30.6 → 0.31.0
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/dist/adapter/hono-adapter.d.ts +43 -6
- package/dist/adapter/hono-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +44 -8
- package/dist/app.d.ts +11 -55
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +1 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -8
- package/dist/preload.d.ts +10 -1
- package/dist/preload.d.ts.map +1 -1
- package/dist/render.d.ts +1 -1
- package/dist/scripts.d.ts +76 -0
- package/dist/scripts.d.ts.map +1 -1
- package/dist/scripts.js +81 -17
- package/dist/vite.d.ts +37 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +2749 -0
- package/package.json +19 -11
- package/src/__tests__/scaffold.test.ts +9 -2
- package/src/__tests__/script-assets.test.ts +228 -0
- package/src/__tests__/vite.test.ts +144 -0
- package/src/adapter/hono-adapter.ts +134 -15
- package/src/app.ts +12 -73
- package/src/index.ts +1 -1
- package/src/preload.tsx +15 -2
- package/src/render.ts +1 -1
- package/src/scripts.tsx +136 -0
- package/src/vite.ts +210 -0
- package/dist/build.d.ts +0 -65
- package/dist/build.d.ts.map +0 -1
- package/dist/build.js +0 -188137
- package/dist/dev.d.ts +0 -36
- package/dist/dev.d.ts.map +0 -1
- package/dist/dev.js +0 -508
- package/src/__tests__/build.test.ts +0 -299
- package/src/__tests__/dev.test.tsx +0 -123
- package/src/__tests__/import-map.test.ts +0 -98
- package/src/build.ts +0 -230
- package/src/dev.tsx +0 -154
package/src/build.ts
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
// Hono build config factory for barefoot.config.ts
|
|
2
|
-
|
|
3
|
-
import type { BuildOptions } from '@barefootjs/jsx'
|
|
4
|
-
import { HonoAdapter } from './adapter/index.ts'
|
|
5
|
-
import type { HonoAdapterOptions } from './adapter/index.ts'
|
|
6
|
-
|
|
7
|
-
export interface HonoBuildOptions extends BuildOptions {
|
|
8
|
-
/** Inject Hono script collection wrapper (default: true) */
|
|
9
|
-
scriptCollection?: boolean
|
|
10
|
-
/** Base path for client JS script URLs (default: '/static/components/') */
|
|
11
|
-
scriptBasePath?: string
|
|
12
|
-
/** Adapter-specific options passed to HonoAdapter */
|
|
13
|
-
adapterOptions?: HonoAdapterOptions
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Create a BarefootBuildConfig for Hono projects.
|
|
18
|
-
*
|
|
19
|
-
* Uses structural typing — does not import BarefootBuildConfig to avoid
|
|
20
|
-
* circular dependency between @barefootjs/hono and @barefootjs/cli.
|
|
21
|
-
*/
|
|
22
|
-
export function createConfig(options: HonoBuildOptions = {}) {
|
|
23
|
-
const useScriptCollection = options.scriptCollection ?? true
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
adapter: new HonoAdapter(options.adapterOptions),
|
|
27
|
-
paths: options.paths,
|
|
28
|
-
components: options.components,
|
|
29
|
-
outDir: options.outDir,
|
|
30
|
-
minify: options.minify,
|
|
31
|
-
contentHash: options.contentHash,
|
|
32
|
-
externals: options.externals,
|
|
33
|
-
externalsBasePath: options.externalsBasePath,
|
|
34
|
-
bundleEntries: options.bundleEntries,
|
|
35
|
-
localImportPrefixes: options.localImportPrefixes,
|
|
36
|
-
transformMarkedTemplate: useScriptCollection
|
|
37
|
-
? (content: string, componentId: string, clientJsPath: string) =>
|
|
38
|
-
addScriptCollection(content, componentId, clientJsPath, options.scriptBasePath)
|
|
39
|
-
: undefined,
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Add Hono script collection wrapper to an SSR marked template.
|
|
45
|
-
* Injects imports, a helper function, and script collector into each
|
|
46
|
-
* exported component function.
|
|
47
|
-
*/
|
|
48
|
-
export function addScriptCollection(content: string, componentId: string, clientJsPath: string, scriptBasePath: string = '/static/components/'): string {
|
|
49
|
-
const basePath = scriptBasePath.endsWith('/') ? scriptBasePath : scriptBasePath + '/'
|
|
50
|
-
const importStatement = "import { useRequestContext } from 'hono/jsx-renderer'\nimport { Fragment } from 'hono/jsx'\n"
|
|
51
|
-
|
|
52
|
-
// Find the last import statement and add our import after it
|
|
53
|
-
const importMatch = content.match(/^([\s\S]*?)((?:import[^\n]+\n)*)/m)
|
|
54
|
-
if (!importMatch) {
|
|
55
|
-
return content
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const beforeImports = importMatch[1]
|
|
59
|
-
const existingImports = importMatch[2]
|
|
60
|
-
const restOfFile = content.slice(importMatch[0].length)
|
|
61
|
-
|
|
62
|
-
// Helper function to wrap JSX with inline script tags (for Suspense streaming)
|
|
63
|
-
const helperFn = `
|
|
64
|
-
function __bfWrap(jsx: any, scripts: string[]) {
|
|
65
|
-
if (scripts.length === 0) return jsx
|
|
66
|
-
return <Fragment>{jsx}{scripts.map(s => <script type="module" src={s} />)}</Fragment>
|
|
67
|
-
}
|
|
68
|
-
`
|
|
69
|
-
|
|
70
|
-
// Script collection code to insert at the start of each component function.
|
|
71
|
-
// When BfScripts has already rendered (e.g., inside Suspense boundaries),
|
|
72
|
-
// scripts are output inline instead of being collected.
|
|
73
|
-
const scriptCollector = `
|
|
74
|
-
let __bfInlineScripts: string[] = []
|
|
75
|
-
// Script collection for client JS hydration
|
|
76
|
-
try {
|
|
77
|
-
const __c = useRequestContext()
|
|
78
|
-
const __scripts: { src: string }[] = __c.get('bfCollectedScripts') || []
|
|
79
|
-
const __outputScripts: Set<string> = __c.get('bfOutputScripts') || new Set()
|
|
80
|
-
const __bfRendered = __c.get('bfScriptsRendered')
|
|
81
|
-
if (!__outputScripts.has('__barefoot__')) {
|
|
82
|
-
__outputScripts.add('__barefoot__')
|
|
83
|
-
if (__bfRendered) __bfInlineScripts.push('${basePath}barefoot.js')
|
|
84
|
-
else __scripts.push({ src: '${basePath}barefoot.js' })
|
|
85
|
-
}
|
|
86
|
-
if (!__outputScripts.has('${componentId}')) {
|
|
87
|
-
__outputScripts.add('${componentId}')
|
|
88
|
-
if (__bfRendered) __bfInlineScripts.push('${basePath}${clientJsPath}')
|
|
89
|
-
else __scripts.push({ src: '${basePath}${clientJsPath}' })
|
|
90
|
-
}
|
|
91
|
-
__c.set('bfCollectedScripts', __scripts)
|
|
92
|
-
__c.set('bfOutputScripts', __outputScripts)
|
|
93
|
-
} catch {}
|
|
94
|
-
`
|
|
95
|
-
|
|
96
|
-
// Insert script collector at the start of each component function body.
|
|
97
|
-
// Matches both exported and non-exported PascalCase components (#786).
|
|
98
|
-
// Uses paren counting instead of regex to correctly handle nested
|
|
99
|
-
// delimiters in destructured params (e.g. `onInput = () => {}`).
|
|
100
|
-
//
|
|
101
|
-
// The regex matches against a comment-masked copy so a docstring
|
|
102
|
-
// example like `function MyNode(this: HTMLElement, props)` is NOT
|
|
103
|
-
// misread as a real function declaration (#1236). The paren counter
|
|
104
|
-
// still walks the ORIGINAL `restOfFile` and keeps the quote-skip
|
|
105
|
-
// logic — TS parameter type annotations contain balanced strings
|
|
106
|
-
// (e.g. `"data-key"?: string`) that the skip handles correctly.
|
|
107
|
-
let modifiedRest = restOfFile
|
|
108
|
-
const maskedRest = maskComments(restOfFile)
|
|
109
|
-
const exportFuncPattern = /(?:export )?function ([A-Z]\w*)\s*\(/g
|
|
110
|
-
const insertions: Array<{ index: number; text: string }> = []
|
|
111
|
-
let efMatch: RegExpExecArray | null
|
|
112
|
-
while ((efMatch = exportFuncPattern.exec(maskedRest)) !== null) {
|
|
113
|
-
const openParenPos = efMatch.index + efMatch[0].length - 1
|
|
114
|
-
// Count parens to find matching ')'
|
|
115
|
-
let depth = 1
|
|
116
|
-
let i = openParenPos + 1
|
|
117
|
-
while (i < restOfFile.length && depth > 0) {
|
|
118
|
-
const ch = restOfFile[i]
|
|
119
|
-
if (ch === "'" || ch === '"' || ch === '`') {
|
|
120
|
-
i++
|
|
121
|
-
while (i < restOfFile.length) {
|
|
122
|
-
if (restOfFile[i] === '\\') { i += 2; continue }
|
|
123
|
-
if (restOfFile[i] === ch) { i++; break }
|
|
124
|
-
i++
|
|
125
|
-
}
|
|
126
|
-
continue
|
|
127
|
-
}
|
|
128
|
-
if (ch === '(') depth++
|
|
129
|
-
else if (ch === ')') depth--
|
|
130
|
-
i++
|
|
131
|
-
}
|
|
132
|
-
// i is now right after matching ')'; find the next '{' for function body
|
|
133
|
-
while (i < restOfFile.length && restOfFile[i] !== '{') i++
|
|
134
|
-
if (i < restOfFile.length) {
|
|
135
|
-
insertions.push({ index: i + 1, text: scriptCollector })
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
// Apply insertions from back to front to preserve indices
|
|
139
|
-
for (let ii = insertions.length - 1; ii >= 0; ii--) {
|
|
140
|
-
const ins = insertions[ii]
|
|
141
|
-
modifiedRest = modifiedRest.slice(0, ins.index) + ins.text + modifiedRest.slice(ins.index)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Wrap each return (...) with __bfWrap((...), __bfInlineScripts).
|
|
145
|
-
//
|
|
146
|
-
// Intentionally NO comment/string masking here. JSX bodies routinely
|
|
147
|
-
// contain unbalanced apostrophes in text content (`Hey! How's it
|
|
148
|
-
// going`) which a string-aware scanner misreads as an open quote and
|
|
149
|
-
// ends up blanking everything until the next stray `'`, breaking
|
|
150
|
-
// paren counting. Plain `(` / `)` counting works for JSX returns
|
|
151
|
-
// because JSX text cannot contain literal parens — those only appear
|
|
152
|
-
// inside `{expr}` slots, which are balanced JS.
|
|
153
|
-
const returnPattern = /return\s*\(/g
|
|
154
|
-
const returnMatches: Array<{ index: number; length: number }> = []
|
|
155
|
-
let m: RegExpExecArray | null
|
|
156
|
-
while ((m = returnPattern.exec(modifiedRest)) !== null) {
|
|
157
|
-
returnMatches.push({ index: m.index, length: m[0].length })
|
|
158
|
-
}
|
|
159
|
-
// Process from last to first to keep earlier offsets valid
|
|
160
|
-
for (let ri = returnMatches.length - 1; ri >= 0; ri--) {
|
|
161
|
-
const rm = returnMatches[ri]
|
|
162
|
-
const afterOpen = rm.index + rm.length // position after 'return ('
|
|
163
|
-
let depth = 1
|
|
164
|
-
let ci = afterOpen
|
|
165
|
-
while (ci < modifiedRest.length && depth > 0) {
|
|
166
|
-
if (modifiedRest[ci] === '(') depth++
|
|
167
|
-
else if (modifiedRest[ci] === ')') depth--
|
|
168
|
-
ci++
|
|
169
|
-
}
|
|
170
|
-
// ci is right after the matching ')'; insert wrap closing there
|
|
171
|
-
modifiedRest = modifiedRest.slice(0, ci) + ', __bfInlineScripts)' + modifiedRest.slice(ci)
|
|
172
|
-
// Replace 'return (' with 'return __bfWrap(('
|
|
173
|
-
modifiedRest = modifiedRest.slice(0, rm.index) + 'return __bfWrap((' + modifiedRest.slice(rm.index + rm.length)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return beforeImports + existingImports + importStatement + helperFn + modifiedRest
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Replace comment contents with spaces (preserving length and newlines
|
|
181
|
-
* so indices computed against the masked text are valid in the
|
|
182
|
-
* original). Used by `addScriptCollection` so its `function Foo(`
|
|
183
|
-
* regex ignores JSDoc / inline comments — a docstring example like
|
|
184
|
-
* `function MyNode(this: HTMLElement, props)` previously masqueraded
|
|
185
|
-
* as a real function declaration (#1236).
|
|
186
|
-
*
|
|
187
|
-
* Handles `//` line comments and `/* ... *\/` block comments (incl.
|
|
188
|
-
* JSDoc). String literals are intentionally NOT masked: JSX text
|
|
189
|
-
* content routinely contains unbalanced apostrophes (`How's`) that a
|
|
190
|
-
* string-aware masker would misread as an open quote, blanking the
|
|
191
|
-
* rest of the file and hiding later function declarations.
|
|
192
|
-
*
|
|
193
|
-
* Strings inside comments are handled implicitly: the whole comment
|
|
194
|
-
* (including any quotes it contains) is blanked.
|
|
195
|
-
*
|
|
196
|
-
* **Known limitation**: this function does NOT track string
|
|
197
|
-
* boundaries, so a `//` or `/*` appearing INSIDE a string literal is
|
|
198
|
-
* still treated as a comment delimiter. Example: in
|
|
199
|
-
* `const u = "https://x.y" ; export function Foo() {}` the `//` in
|
|
200
|
-
* `https://` is misread as a line comment and the rest of the line is
|
|
201
|
-
* blanked — a `function Foo()` on that same line would be hidden from
|
|
202
|
-
* the regex. SSR template output (the only caller) does not embed
|
|
203
|
-
* such cases in practice. If a future caller can produce them, swap
|
|
204
|
-
* in a real lexer rather than extending this helper.
|
|
205
|
-
*/
|
|
206
|
-
export function maskComments(s: string): string {
|
|
207
|
-
let out = ''
|
|
208
|
-
let i = 0
|
|
209
|
-
while (i < s.length) {
|
|
210
|
-
const ch = s[i]
|
|
211
|
-
const next = s[i + 1]
|
|
212
|
-
if (ch === '/' && next === '*') {
|
|
213
|
-
const end = s.indexOf('*/', i + 2)
|
|
214
|
-
const stop = end === -1 ? s.length : end + 2
|
|
215
|
-
for (let j = i; j < stop; j++) out += s[j] === '\n' ? '\n' : ' '
|
|
216
|
-
i = stop
|
|
217
|
-
continue
|
|
218
|
-
}
|
|
219
|
-
if (ch === '/' && next === '/') {
|
|
220
|
-
const end = s.indexOf('\n', i + 2)
|
|
221
|
-
const stop = end === -1 ? s.length : end
|
|
222
|
-
for (let j = i; j < stop; j++) out += ' '
|
|
223
|
-
i = stop
|
|
224
|
-
continue
|
|
225
|
-
}
|
|
226
|
-
out += ch
|
|
227
|
-
i++
|
|
228
|
-
}
|
|
229
|
-
return out
|
|
230
|
-
}
|
package/src/dev.tsx
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BarefootJS Dev Reloader (Hono, Node-side)
|
|
3
|
-
*
|
|
4
|
-
* `createDevReloader` turns `bf build --watch`'s sentinel file
|
|
5
|
-
* (`<distDir>/.dev/build-id`) into an SSE stream:
|
|
6
|
-
*
|
|
7
|
-
* [bf build --watch] → writes `<distDir>/.dev/build-id` after each successful build
|
|
8
|
-
* [createDevReloader] → watches that file, streams SSE `event: reload`
|
|
9
|
-
*
|
|
10
|
-
* Mount it on a Hono route in the generated app; the matching browser-
|
|
11
|
-
* side subscriber (`<DevReload />`) lives in the project itself (see
|
|
12
|
-
* the hono-node scaffold's `dev-reload.tsx`) so its endpoint URL and
|
|
13
|
-
* reconnect behavior are an in-tree edit.
|
|
14
|
-
*
|
|
15
|
-
* ```ts
|
|
16
|
-
* // factory.ts
|
|
17
|
-
* import { createDevReloader } from '@barefootjs/hono/dev'
|
|
18
|
-
* app.get('/_bf/reload', createDevReloader({ distDir: './dist' }))
|
|
19
|
-
* ```
|
|
20
|
-
*
|
|
21
|
-
* Disabled (404) when `NODE_ENV === 'production'` unless `enabled: true`
|
|
22
|
-
* is passed explicitly.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import type { Context } from 'hono'
|
|
26
|
-
import { mkdir, readFile, watch } from 'node:fs/promises'
|
|
27
|
-
import { resolve } from 'node:path'
|
|
28
|
-
|
|
29
|
-
export interface CreateDevReloaderOptions {
|
|
30
|
-
/** Directory that `bf build` writes output into (contains `.dev/build-id`). */
|
|
31
|
-
distDir: string
|
|
32
|
-
/** Override the dev gate. Defaults to `process.env.NODE_ENV !== 'production'`. */
|
|
33
|
-
enabled?: boolean
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Sentinel path contract with `@barefootjs/cli`. These values must match
|
|
37
|
-
// `DEV_SENTINEL_SUBDIR` / `DEV_SENTINEL_FILENAME` in `packages/cli/src/lib/build.ts`
|
|
38
|
-
// — duplicated intentionally to avoid a runtime dep on the CLI.
|
|
39
|
-
const DEV_SUBDIR = '.dev'
|
|
40
|
-
const BUILD_ID_FILE = 'build-id'
|
|
41
|
-
/**
|
|
42
|
-
* Heartbeat interval for idle keepalive. Must stay comfortably under Bun's
|
|
43
|
-
* default 10s idleTimeout — otherwise the server would close a quiet SSE
|
|
44
|
-
* stream and the browser would EventSource-reconnect every cycle, which can
|
|
45
|
-
* lose a rebuild event emitted in the gap between close and reconnect.
|
|
46
|
-
*/
|
|
47
|
-
const HEARTBEAT_MS = 5000
|
|
48
|
-
|
|
49
|
-
function isDevDefault(): boolean {
|
|
50
|
-
return process.env.NODE_ENV !== 'production'
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Hono route handler that streams Server-Sent Events and emits `reload` every
|
|
55
|
-
* time `<distDir>/.dev/build-id` is written. Disabled (404) in production.
|
|
56
|
-
*/
|
|
57
|
-
export function createDevReloader(
|
|
58
|
-
options: CreateDevReloaderOptions,
|
|
59
|
-
): (c: Context) => Response | Promise<Response> {
|
|
60
|
-
const { distDir, enabled = isDevDefault() } = options
|
|
61
|
-
|
|
62
|
-
return async (c: Context) => {
|
|
63
|
-
if (!enabled) return c.notFound()
|
|
64
|
-
|
|
65
|
-
const devDir = resolve(distDir, DEV_SUBDIR)
|
|
66
|
-
// Ensure the directory exists so fs.watch doesn't ENOENT before the first build.
|
|
67
|
-
await mkdir(devDir, { recursive: true })
|
|
68
|
-
|
|
69
|
-
const buildIdPath = resolve(devDir, BUILD_ID_FILE)
|
|
70
|
-
const signal = c.req.raw.signal
|
|
71
|
-
// If the client reconnects with Last-Event-ID (the build-id it last saw)
|
|
72
|
-
// and the current build-id is newer, a rebuild happened while it was
|
|
73
|
-
// disconnected — recover by firing `reload` immediately instead of `hello`.
|
|
74
|
-
const lastEventId = (c.req.header('Last-Event-ID') ?? '').trim()
|
|
75
|
-
|
|
76
|
-
const readBuildId = async (): Promise<string> => {
|
|
77
|
-
try {
|
|
78
|
-
// `readFile(path, 'utf8')` is the obvious call, but Deno's
|
|
79
|
-
// `node:fs/promises` types resolve that positional-encoding
|
|
80
|
-
// overload to `NonSharedBuffer` rather than `string`, so `.trim()`
|
|
81
|
-
// is missing and `deno check` fails (TS2769 + TS2339). Read raw
|
|
82
|
-
// bytes and decode with `TextDecoder` instead: it defaults to
|
|
83
|
-
// UTF-8 (same result as before), sidesteps the broken overload
|
|
84
|
-
// entirely, and is portable across Node/Deno/Workers — matching
|
|
85
|
-
// the `TextEncoder` used just below.
|
|
86
|
-
return new TextDecoder().decode(await readFile(buildIdPath)).trim()
|
|
87
|
-
} catch {
|
|
88
|
-
return ''
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const stream = new ReadableStream<Uint8Array>({
|
|
93
|
-
async start(controller) {
|
|
94
|
-
const encoder = new TextEncoder()
|
|
95
|
-
const send = (chunk: string) => {
|
|
96
|
-
try {
|
|
97
|
-
controller.enqueue(encoder.encode(chunk))
|
|
98
|
-
} catch {
|
|
99
|
-
// Stream already closed (client disconnected).
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
send(`retry: 1000\n\n`)
|
|
104
|
-
let lastSentId = ''
|
|
105
|
-
const initialId = await readBuildId()
|
|
106
|
-
if (initialId) {
|
|
107
|
-
lastSentId = initialId
|
|
108
|
-
const event = lastEventId && lastEventId !== initialId ? 'reload' : 'hello'
|
|
109
|
-
send(`event: ${event}\nid: ${initialId}\ndata: ${initialId}\n\n`)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Heartbeat keeps the connection under Bun's idleTimeout so that a
|
|
113
|
-
// silent period between builds doesn't close the socket (which would
|
|
114
|
-
// otherwise race with in-flight rebuilds and drop `reload` events).
|
|
115
|
-
const heartbeat = setInterval(() => send(`: hb\n\n`), HEARTBEAT_MS)
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
// Watch the parent directory: the build-id file may not exist yet,
|
|
119
|
-
// and `fs.watch` on a missing path throws.
|
|
120
|
-
const iter = watch(devDir, { signal })
|
|
121
|
-
for await (const event of iter) {
|
|
122
|
-
if (event.filename !== BUILD_ID_FILE) continue
|
|
123
|
-
const id = await readBuildId()
|
|
124
|
-
if (!id || id === lastSentId) continue
|
|
125
|
-
lastSentId = id
|
|
126
|
-
send(`event: reload\nid: ${id}\ndata: ${id}\n\n`)
|
|
127
|
-
}
|
|
128
|
-
} catch (err) {
|
|
129
|
-
const name = (err as { name?: string } | undefined)?.name
|
|
130
|
-
if (name !== 'AbortError') {
|
|
131
|
-
const message = (err as Error).message ?? 'watch error'
|
|
132
|
-
send(`event: error\ndata: ${JSON.stringify(message)}\n\n`)
|
|
133
|
-
}
|
|
134
|
-
} finally {
|
|
135
|
-
clearInterval(heartbeat)
|
|
136
|
-
try { controller.close() } catch { /* already closed */ }
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
cancel() {
|
|
140
|
-
// Client disconnected; fs.watch will unwind via `signal`.
|
|
141
|
-
},
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
return new Response(stream, {
|
|
145
|
-
headers: {
|
|
146
|
-
'Content-Type': 'text/event-stream',
|
|
147
|
-
'Cache-Control': 'no-cache, no-transform',
|
|
148
|
-
'Connection': 'keep-alive',
|
|
149
|
-
'X-Accel-Buffering': 'no',
|
|
150
|
-
},
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|