beauticode-dsh 1.0.3 → 1.0.4

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/cli.js CHANGED
@@ -78,19 +78,66 @@ function packageInsert() {
78
78
  ].join("\n");
79
79
  }
80
80
 
81
- function hasBridge(text) {
82
- return new RegExp(`^\\s*-\\s*id:\\s*${bridgeId}\\s*$`, "m").test(text);
83
- }
84
-
85
- function stripBridge(text) {
86
- const patterns = [
87
- /(?:^|\r?\n)# beauticode-bridge \(installer\)\r?\n- insert:\r?\n(?:[ \t]+.*\r?\n)*/g,
88
- /(?:^|\r?\n)- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/g,
89
- ];
90
- let next = text;
91
- for (const pattern of patterns) next = next.replace(pattern, "\n");
92
- return next;
93
- }
81
+ function hasBridge(text) {
82
+ return new RegExp(`^\\s*-\\s*id:\\s*${bridgeId}\\s*$`, "m").test(text);
83
+ }
84
+
85
+ function stripBridge(text) {
86
+ const lines = text.split(/\r?\n/);
87
+ const removed = new Array(lines.length).fill(false);
88
+ const indentOf = (line) => line.match(/^[ \t]*/)?.[0].length ?? 0;
89
+
90
+ for (let i = 0; i < lines.length; i += 1) {
91
+ const insert = lines[i].match(/^([ \t]*)-\s*insert:\s*(?:#.*)?$/);
92
+ if (!insert) continue;
93
+ const insertIndent = insert[1].length;
94
+ let blockEnd = i + 1;
95
+ while (blockEnd < lines.length) {
96
+ const line = lines[blockEnd];
97
+ if (line.trim() && indentOf(line) <= insertIndent) break;
98
+ blockEnd += 1;
99
+ }
100
+
101
+ let foundBridge = false;
102
+ for (let j = i + 1; j < blockEnd; ) {
103
+ const bridge = lines[j].match(
104
+ /^([ \t]*)-\s*id:\s*beauticode-bridge\s*(?:#.*)?$/,
105
+ );
106
+ if (!bridge || bridge[1].length <= insertIndent) {
107
+ j += 1;
108
+ continue;
109
+ }
110
+ foundBridge = true;
111
+ const itemIndent = bridge[1].length;
112
+ let itemEnd = j + 1;
113
+ while (itemEnd < blockEnd) {
114
+ const line = lines[itemEnd];
115
+ if (line.trim() && indentOf(line) <= itemIndent) break;
116
+ itemEnd += 1;
117
+ }
118
+ for (let k = j; k < itemEnd; k += 1) removed[k] = true;
119
+ j = itemEnd;
120
+ }
121
+
122
+ if (!foundBridge) continue;
123
+ const marker = `${insert[1]}# beauticode-bridge (installer)`;
124
+ if (i > 0 && lines[i - 1].trimEnd() === marker) removed[i - 1] = true;
125
+ const hasSibling = lines
126
+ .slice(i + 1, blockEnd)
127
+ .some(
128
+ (line, offset) =>
129
+ !removed[i + 1 + offset] &&
130
+ line.trim() !== "" &&
131
+ !line.trimStart().startsWith("#"),
132
+ );
133
+ if (!hasSibling) {
134
+ for (let k = i; k < blockEnd; k += 1) removed[k] = true;
135
+ }
136
+ i = blockEnd - 1;
137
+ }
138
+
139
+ return lines.filter((_, index) => !removed[index]).join("\n");
140
+ }
94
141
 
95
142
  function overlayPayload(text) {
96
143
  return text
@@ -196,6 +243,83 @@ async function ensureEngine(dest) {
196
243
  await stageEngineInto(dest);
197
244
  }
198
245
 
246
+ function pluginLinkPath(webProfile) {
247
+ return path.join(webProfile, "node_modules", pluginName);
248
+ }
249
+
250
+ function legacyPluginLinkPath(webProfile) {
251
+ return path.join(webProfile, "node_modules", "@beauticode", "dsh-plugin");
252
+ }
253
+
254
+ function linkSpecFor(pluginHome) {
255
+ return `link:${path.resolve(pluginHome).replaceAll("\\", "/")}`;
256
+ }
257
+
258
+ async function sameLinkTarget(link, dest) {
259
+ try {
260
+ const stat = await fsp.lstat(link);
261
+ if (stat.isSymbolicLink()) {
262
+ const target = await fsp.readlink(link);
263
+ return path.resolve(path.dirname(link), target) === path.resolve(dest);
264
+ }
265
+ if (stat.isDirectory()) {
266
+ return path.resolve(link) === path.resolve(dest);
267
+ }
268
+ } catch {
269
+ return false;
270
+ }
271
+ return false;
272
+ }
273
+
274
+ async function linkPluginIntoProfile(webProfile, dest) {
275
+ const link = pluginLinkPath(webProfile);
276
+ const legacy = legacyPluginLinkPath(webProfile);
277
+ if (fs.existsSync(legacy)) {
278
+ await fsp.rm(legacy, { recursive: true, force: true });
279
+ }
280
+ await fsp.mkdir(path.dirname(link), { recursive: true });
281
+ if (fs.existsSync(link)) {
282
+ if (await sameLinkTarget(link, dest)) return;
283
+ await fsp.rm(link, { recursive: true, force: true });
284
+ }
285
+ const type = process.platform === "win32" ? "junction" : "dir";
286
+ await fsp.symlink(path.resolve(dest), link, type);
287
+ }
288
+
289
+ async function ensureWebPackageDep(webPackage, pluginHome) {
290
+ const raw = await fsp.readFile(webPackage, "utf8");
291
+ const json = JSON.parse(raw);
292
+ if (!json.dependencies || typeof json.dependencies !== "object" || Array.isArray(json.dependencies)) {
293
+ json.dependencies = {};
294
+ }
295
+ const spec = linkSpecFor(pluginHome);
296
+ const hadLegacy = Object.prototype.hasOwnProperty.call(
297
+ json.dependencies,
298
+ "@beauticode/dsh-plugin",
299
+ );
300
+ delete json.dependencies["@beauticode/dsh-plugin"];
301
+ const current = json.dependencies[pluginName];
302
+ if (current === spec && !hadLegacy) return;
303
+ json.dependencies[pluginName] = spec;
304
+ await fsp.writeFile(webPackage, `${JSON.stringify(json, null, 2)}\n`, "utf8");
305
+ }
306
+
307
+ async function removeWebPackageDep(webPackage) {
308
+ if (!fs.existsSync(webPackage)) return false;
309
+ const json = JSON.parse(await fsp.readFile(webPackage, "utf8"));
310
+ if (!json.dependencies || typeof json.dependencies !== "object") return false;
311
+ let changed = false;
312
+ for (const name of [pluginName, "@beauticode/dsh-plugin"]) {
313
+ if (Object.prototype.hasOwnProperty.call(json.dependencies, name)) {
314
+ delete json.dependencies[name];
315
+ changed = true;
316
+ }
317
+ }
318
+ if (!changed) return false;
319
+ await fsp.writeFile(webPackage, `${JSON.stringify(json, null, 2)}\n`, "utf8");
320
+ return true;
321
+ }
322
+
199
323
  async function install(opts) {
200
324
  const dest = path.resolve(opts.pluginHome);
201
325
  const dshHome = path.resolve(opts.dshHome);
@@ -213,7 +337,9 @@ async function install(opts) {
213
337
  }
214
338
 
215
339
  if (fs.existsSync(webPackage)) {
216
- await writePatch(webPatch, fileUriInsert(toFileUri(indexFile)));
340
+ await linkPluginIntoProfile(webProfile, dest);
341
+ await ensureWebPackageDep(webPackage, dest);
342
+ await writePatch(webPatch, packageInsert());
217
343
  if (fs.existsSync(homePatch)) {
218
344
  const homeRaw = await fsp.readFile(homePatch, "utf8");
219
345
  if (hasBridge(homeRaw)) await removePatch(homePatch);
@@ -234,13 +360,23 @@ async function install(opts) {
234
360
 
235
361
  async function uninstall(opts) {
236
362
  const dshHome = path.resolve(opts.dshHome);
363
+ const webProfile = path.join(dshHome, "profiles", "web");
237
364
  const removed = [];
238
- if (await removePatch(path.join(dshHome, "profiles", "web", "cordis.patch.yml"))) {
365
+ if (await removePatch(path.join(webProfile, "cordis.patch.yml"))) {
239
366
  removed.push("web patch");
240
367
  }
241
368
  if (await removePatch(path.join(dshHome, "cordis.patch.yml"))) {
242
369
  removed.push("home patch");
243
370
  }
371
+ if (await removeWebPackageDep(path.join(webProfile, "package.json"))) {
372
+ removed.push("web package.json");
373
+ }
374
+ for (const link of [pluginLinkPath(webProfile), legacyPluginLinkPath(webProfile)]) {
375
+ if (fs.existsSync(link)) {
376
+ await fsp.rm(link, { recursive: true, force: true });
377
+ removed.push(link);
378
+ }
379
+ }
244
380
  const dest = path.resolve(opts.pluginHome);
245
381
  if (fs.existsSync(dest)) {
246
382
  await fsp.rm(dest, { recursive: true, force: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beauticode-dsh",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Cordis plugin: image/video backgrounds for DeepSeek Harness web.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
@@ -181,7 +181,11 @@ export class DshSession {
181
181
  }
182
182
  }
183
183
  if (manifest.background) {
184
- stagedImage = await this.media.stage(path.join(this.store.paths.activeDir, manifest.background.image));
184
+ const runtimeImagePath = await this.store.prepareRuntimeImage(manifest);
185
+ if (!runtimeImagePath) {
186
+ throw new Error("DSH image reapply requires a detached runtime copy.");
187
+ }
188
+ stagedImage = await this.media.stage(runtimeImagePath);
185
189
  if (manifest.background.type === "video" && manifest.background.video) {
186
190
  const runtimeVideoPath = await this.store.prepareRuntimeVideo(manifest);
187
191
  if (!runtimeVideoPath) {
@@ -103,7 +103,7 @@ export class ApplyTransaction {
103
103
  }
104
104
  }
105
105
  await this.media.commit(staged);
106
- await this.store.pruneRuntimeMedia(runtimeVideoPath);
106
+ await this.store.pruneRuntimeMedia(runtimeKeepPaths(staged, runtimeVideoPath));
107
107
  staged = null;
108
108
  if (snapshot) {
109
109
  await this.store.clearSnapshot(snapshot);
@@ -141,13 +141,15 @@ export class ApplyTransaction {
141
141
  if (!manifest.background) {
142
142
  return { image: null, video: null };
143
143
  }
144
- const imagePath = path.join(this.store.paths.activeDir, manifest.background.image);
145
- const image = await this.media.stage(imagePath);
144
+ // Serve stills and videos from runtime-media copies so Windows can rotate
145
+ // `active/` while the previous generation is still being read.
146
+ const runtimeImagePath = await this.store.prepareRuntimeImage(manifest);
147
+ if (!runtimeImagePath) {
148
+ throw new Error("Image media staging requires a detached runtime copy.");
149
+ }
150
+ const image = await this.media.stage(runtimeImagePath);
146
151
  let video = null;
147
152
  if (manifest.background.type === "video" && manifest.background.video) {
148
- // DSH streams this handle for the lifetime of the active <video>. On
149
- // Windows, serving active/background.mp4 directly keeps the active
150
- // directory open and the next atomic directory swap fails with EPERM.
151
153
  const runtimeVideoPath = await this.store.prepareRuntimeVideo(manifest);
152
154
  if (!runtimeVideoPath) {
153
155
  throw new Error("Video media staging requires a detached runtime copy.");
@@ -188,7 +190,7 @@ export class ApplyTransaction {
188
190
  }
189
191
  }
190
192
  await this.media.commit(restoredMedia);
191
- await this.store.pruneRuntimeMedia(runtimeVideoPath);
193
+ await this.store.pruneRuntimeMedia(runtimeKeepPaths(restoredMedia, runtimeVideoPath));
192
194
  restoredMedia = null;
193
195
  await this.store.clearSnapshot(snapshot);
194
196
  }
@@ -197,6 +199,16 @@ export class ApplyTransaction {
197
199
  }
198
200
  }
199
201
  }
202
+ function runtimeKeepPaths(staged, extra) {
203
+ const keep = [];
204
+ if (staged?.image?.filePath)
205
+ keep.push(staged.image.filePath);
206
+ if (staged?.video?.filePath)
207
+ keep.push(staged.video.filePath);
208
+ if (extra)
209
+ keep.push(extra);
210
+ return keep;
211
+ }
200
212
  export async function buildHostApplyPayload(store, manifest, staged, cssText, videoStartAt) {
201
213
  if (!manifest.background) {
202
214
  return {
@@ -56,7 +56,7 @@ export class BackgroundStore {
56
56
  maxSavedBytes;
57
57
  bundledThemes;
58
58
  #runtimeSessionName = `session-${process.pid}-${crypto.randomUUID()}`;
59
- #runtimeVideoCache = new Map();
59
+ #runtimeMediaCache = new Map();
60
60
  #writeChain = Promise.resolve();
61
61
  #writeContext = new AsyncLocalStorage();
62
62
  constructor(opts = {}) {
@@ -242,6 +242,24 @@ export class BackgroundStore {
242
242
  this.#writeChain = run.then(() => undefined, () => undefined);
243
243
  return run;
244
244
  }
245
+ /**
246
+ * Copy the active still to a renderer-only path outside `active`.
247
+ *
248
+ * Serving `active/poster.png` keeps that directory open on Windows, so the
249
+ * next atomic active-directory rotation fails with EPERM. The detached copy
250
+ * matches the video runtime path.
251
+ */
252
+ async prepareRuntimeImage(manifest) {
253
+ if (!manifest.background?.image)
254
+ return null;
255
+ return this.#prepareRuntimeMedia({
256
+ generation: manifest.generation,
257
+ kind: "image",
258
+ sourceName: manifest.background.image,
259
+ ext: path.extname(manifest.background.image) || ".png",
260
+ validate: (filePath) => validateImageFile(filePath),
261
+ });
262
+ }
245
263
  /**
246
264
  * Copy the active video to a renderer-only path outside `active`.
247
265
  *
@@ -251,57 +269,73 @@ export class BackgroundStore {
251
269
  * renderer lifetime independent from the disk transaction.
252
270
  */
253
271
  async prepareRuntimeVideo(manifest) {
272
+ if (manifest.background?.type !== "video" ||
273
+ !manifest.background.video) {
274
+ return null;
275
+ }
276
+ return this.#prepareRuntimeMedia({
277
+ generation: manifest.generation,
278
+ kind: "video",
279
+ sourceName: manifest.background.video,
280
+ ext: ".mp4",
281
+ validate: (filePath) => validateVideoFile(filePath),
282
+ });
283
+ }
284
+ async #prepareRuntimeMedia(opts) {
254
285
  return this.#withWriteLock(async () => {
255
- if (manifest.background?.type !== "video" ||
256
- !manifest.background.video) {
257
- return null;
258
- }
259
286
  await this.init();
260
- const cached = this.#runtimeVideoCache.get(manifest.generation);
287
+ const cacheKey = `${opts.kind}:${opts.generation}`;
288
+ const cached = this.#runtimeMediaCache.get(cacheKey);
261
289
  if (cached) {
262
290
  try {
263
- await validateVideoFile(cached);
291
+ await opts.validate(cached);
264
292
  return cached;
265
293
  }
266
294
  catch {
267
- this.#runtimeVideoCache.delete(manifest.generation);
295
+ this.#runtimeMediaCache.delete(cacheKey);
268
296
  }
269
297
  }
270
- const source = path.join(this.paths.activeDir, manifest.background.video);
271
- const validated = await validateVideoFile(source);
272
- const sessionDir = path.join(this.paths.runtimeMediaDir, this.#runtimeSessionName);
273
- if (!isPathInsideRoot(this.paths.runtimeMediaDir, sessionDir)) {
274
- throw new MediaValidationError("Runtime media path escaped runtime-media root.");
275
- }
276
- const runtimeRootStat = await fs.lstat(this.paths.runtimeMediaDir);
277
- if (!runtimeRootStat.isDirectory() ||
278
- runtimeRootStat.isSymbolicLink()) {
279
- throw new MediaValidationError("Runtime media root must be a real directory.");
280
- }
281
- await fs.mkdir(sessionDir, { recursive: true });
282
- const sessionStat = await fs.lstat(sessionDir);
283
- if (!sessionStat.isDirectory() || sessionStat.isSymbolicLink()) {
284
- throw new MediaValidationError("Runtime media session must be a real directory.");
285
- }
286
- const [realRuntimeRoot, realSessionDir] = await Promise.all([
287
- fs.realpath(this.paths.runtimeMediaDir),
288
- fs.realpath(sessionDir),
289
- ]);
290
- if (!isPathInsideRoot(realRuntimeRoot, realSessionDir)) {
291
- throw new MediaValidationError("Runtime media session escaped runtime-media root.");
292
- }
293
- const destination = path.join(sessionDir, `video-${manifest.generation}-${validated.identity.slice(0, 16)}.mp4`);
298
+ const source = path.join(this.paths.activeDir, opts.sourceName);
299
+ const validated = await opts.validate(source);
300
+ const sessionDir = await this.#ensureRuntimeSessionDir();
301
+ const safeExt = opts.ext.toLowerCase().startsWith(".")
302
+ ? opts.ext.toLowerCase()
303
+ : `.${opts.ext}`;
304
+ const destination = path.join(sessionDir, `${opts.kind}-${opts.generation}-${validated.identity.slice(0, 16)}${safeExt}`);
294
305
  await copyFileAtomic(validated.filePath, destination);
295
- const copied = await validateVideoFile(destination);
306
+ const copied = await opts.validate(destination);
296
307
  if (copied.identity !== validated.identity) {
297
308
  await fs.rm(destination, { force: true }).catch(() => { });
298
- throw new MediaValidationError("Runtime video copy failed its identity check.");
309
+ throw new MediaValidationError(`Runtime ${opts.kind} copy failed its identity check.`);
299
310
  }
300
- this.#runtimeVideoCache.set(manifest.generation, destination);
301
- await this.#pruneRuntimeMediaUnlocked(destination);
311
+ this.#runtimeMediaCache.set(cacheKey, destination);
312
+ await this.#pruneRuntimeMediaUnlocked([...this.#runtimeMediaCache.values()]);
302
313
  return destination;
303
314
  });
304
315
  }
316
+ async #ensureRuntimeSessionDir() {
317
+ const sessionDir = path.join(this.paths.runtimeMediaDir, this.#runtimeSessionName);
318
+ if (!isPathInsideRoot(this.paths.runtimeMediaDir, sessionDir)) {
319
+ throw new MediaValidationError("Runtime media path escaped runtime-media root.");
320
+ }
321
+ const runtimeRootStat = await fs.lstat(this.paths.runtimeMediaDir);
322
+ if (!runtimeRootStat.isDirectory() || runtimeRootStat.isSymbolicLink()) {
323
+ throw new MediaValidationError("Runtime media root must be a real directory.");
324
+ }
325
+ await fs.mkdir(sessionDir, { recursive: true });
326
+ const sessionStat = await fs.lstat(sessionDir);
327
+ if (!sessionStat.isDirectory() || sessionStat.isSymbolicLink()) {
328
+ throw new MediaValidationError("Runtime media session must be a real directory.");
329
+ }
330
+ const [realRuntimeRoot, realSessionDir] = await Promise.all([
331
+ fs.realpath(this.paths.runtimeMediaDir),
332
+ fs.realpath(sessionDir),
333
+ ]);
334
+ if (!isPathInsideRoot(realRuntimeRoot, realSessionDir)) {
335
+ throw new MediaValidationError("Runtime media session escaped runtime-media root.");
336
+ }
337
+ return sessionDir;
338
+ }
305
339
  /**
306
340
  * Best-effort cleanup. Locked stale files are retained and retried on the
307
341
  * next apply/start instead of turning successful theme switching into an
@@ -310,14 +344,15 @@ export class BackgroundStore {
310
344
  async pruneRuntimeMedia(keepPath = null) {
311
345
  await this.#withWriteLock(async () => {
312
346
  await this.init();
313
- await this.#pruneRuntimeMediaUnlocked(keepPath);
347
+ const keep = (Array.isArray(keepPath) ? keepPath : [keepPath]).filter((value) => typeof value === "string" && value.length > 0);
348
+ await this.#pruneRuntimeMediaUnlocked(keep);
314
349
  });
315
350
  }
316
- async #pruneRuntimeMediaUnlocked(keepPath) {
351
+ async #pruneRuntimeMediaUnlocked(keepPaths) {
317
352
  const runtimeRoot = path.resolve(this.paths.runtimeMediaDir);
318
- const keep = keepPath && isPathInsideRoot(runtimeRoot, keepPath)
319
- ? path.resolve(keepPath)
320
- : null;
353
+ const keep = new Set(keepPaths
354
+ .filter((value) => isPathInsideRoot(runtimeRoot, value))
355
+ .map((value) => path.resolve(value)));
321
356
  let sessions;
322
357
  try {
323
358
  sessions = await fs.readdir(runtimeRoot);
@@ -331,7 +366,8 @@ export class BackgroundStore {
331
366
  const sessionDir = path.join(runtimeRoot, name);
332
367
  if (!isPathInsideRoot(runtimeRoot, sessionDir))
333
368
  continue;
334
- if (!keep || !isPathInsideRoot(sessionDir, keep)) {
369
+ const keepInSession = [...keep].some((file) => isPathInsideRoot(sessionDir, file));
370
+ if (!keepInSession) {
335
371
  await rmrf(sessionDir).catch(() => { });
336
372
  continue;
337
373
  }
@@ -344,15 +380,15 @@ export class BackgroundStore {
344
380
  }
345
381
  for (const file of files) {
346
382
  const candidate = path.resolve(sessionDir, file);
347
- if (candidate === keep || !isPathInsideRoot(sessionDir, candidate)) {
383
+ if (keep.has(candidate) || !isPathInsideRoot(sessionDir, candidate)) {
348
384
  continue;
349
385
  }
350
386
  await fs.rm(candidate, { recursive: true, force: true }).catch(() => { });
351
387
  }
352
388
  }
353
- for (const [generation, cached] of this.#runtimeVideoCache) {
354
- if (path.resolve(cached) !== keep) {
355
- this.#runtimeVideoCache.delete(generation);
389
+ for (const [key, cached] of this.#runtimeMediaCache) {
390
+ if (!keep.has(path.resolve(cached))) {
391
+ this.#runtimeMediaCache.delete(key);
356
392
  }
357
393
  }
358
394
  }
@@ -36,6 +36,27 @@ function parseRange(value, size) {
36
36
  function makeToken() {
37
37
  return randomUUID().replaceAll("-", "");
38
38
  }
39
+ function pipeOpenedFile(handle, response, range) {
40
+ const stream = handle.createReadStream({
41
+ start: range.start,
42
+ ...(range.end != null ? { end: range.end } : {}),
43
+ autoClose: false,
44
+ });
45
+ let closed = false;
46
+ const closeHandle = () => {
47
+ if (closed)
48
+ return;
49
+ closed = true;
50
+ void handle.close().catch(() => { });
51
+ };
52
+ stream.on("error", () => {
53
+ closeHandle();
54
+ response.destroy();
55
+ });
56
+ stream.on("close", closeHandle);
57
+ response.on("close", closeHandle);
58
+ stream.pipe(response);
59
+ }
39
60
  async function hashOpenedFile(handle, size) {
40
61
  const hash = createHash("sha256");
41
62
  const chunk = Buffer.allocUnsafe(1024 * 1024);
@@ -401,11 +422,8 @@ export class LoopbackMediaHub {
401
422
  opened = null;
402
423
  return;
403
424
  }
404
- const stream = opened.createReadStream({ start: 0, autoClose: true });
425
+ pipeOpenedFile(opened, response, { start: 0 });
405
426
  opened = null;
406
- stream
407
- .on("error", () => response.destroy())
408
- .pipe(response);
409
427
  return;
410
428
  }
411
429
  headers["Content-Range"] =
@@ -418,15 +436,11 @@ export class LoopbackMediaHub {
418
436
  opened = null;
419
437
  return;
420
438
  }
421
- const stream = opened.createReadStream({
439
+ pipeOpenedFile(opened, response, {
422
440
  start: range.start,
423
441
  end: range.end,
424
- autoClose: true,
425
442
  });
426
443
  opened = null;
427
- stream
428
- .on("error", () => response.destroy())
429
- .pipe(response);
430
444
  }
431
445
  catch {
432
446
  await opened?.close().catch(() => { });