md-task-viewer 0.1.8 → 0.1.10

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.
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Markdown Task Viewer</title>
7
- <script type="module" crossorigin src="/assets/index-DDUTU9wi.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-BfBV_hRv.css">
7
+ <script type="module" crossorigin src="/assets/index-j5ntXWRS.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-CSNRoovR.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/dist/server.js CHANGED
@@ -2,32 +2,25 @@
2
2
  import Fastify from "fastify";
3
3
  import fastifyStatic from "@fastify/static";
4
4
  import chokidar from "chokidar";
5
- import path3 from "path";
5
+ import path7 from "path";
6
6
  import { fileURLToPath } from "url";
7
7
 
8
- // src/taskStore.ts
9
- import matter from "gray-matter";
10
- import picomatch from "picomatch";
11
- import path from "path";
12
- import { promises as fs } from "fs";
13
-
14
- // src/types.ts
15
- var CONFIG_FILE_NAME = ".md-task-viewer.json";
16
-
17
8
  // src/slugify.ts
18
9
  function slugify(value) {
19
10
  const slug = value.normalize("NFC").replace(/[\s\u3000]+/g, "-").replace(/[^\p{L}\p{N}-]+/gu, "").replace(/^-+|-+$/g, "");
20
11
  return slug || "untitled-task";
21
12
  }
22
13
 
23
- // src/taskStore.ts
24
- var MARKDOWN_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown"]);
25
- var REQUIRED_PRIORITY = ["MUST", "WANT"];
26
- var REQUIRED_STATUS = ["TODO", "WIP", "DONE"];
14
+ // src/taskStore/paths.ts
15
+ import path from "path";
16
+
17
+ // src/taskStore/errors.ts
27
18
  var ConflictError = class extends Error {
28
19
  };
29
20
  var ValidationError = class extends Error {
30
21
  };
22
+
23
+ // src/taskStore/paths.ts
31
24
  function toPosixPath(filePath) {
32
25
  return filePath.split(path.sep).join("/");
33
26
  }
@@ -41,11 +34,30 @@ function normalizeRelativePath(candidate) {
41
34
  function ensureMarkdownExtension(filePath) {
42
35
  return path.posix.extname(filePath) ? filePath : `${filePath}.md`;
43
36
  }
37
+
38
+ // src/taskStore/frontmatter.ts
39
+ import matter from "gray-matter";
40
+ import path2 from "path";
41
+ import { promises as fs } from "fs";
42
+ var REQUIRED_PRIORITY = ["MUST", "WANT"];
43
+ var REQUIRED_STATUS = ["TODO", "DONE"];
44
+ function isValidPriority(value) {
45
+ return REQUIRED_PRIORITY.includes(value);
46
+ }
47
+ function isValidStatus(value) {
48
+ return REQUIRED_STATUS.includes(value);
49
+ }
50
+ function ensureRequiredStatus(status) {
51
+ if (!isValidStatus(status)) {
52
+ throw new ValidationError("Status must be TODO or DONE.");
53
+ }
54
+ return status;
55
+ }
44
56
  function asUtcISOString(date) {
45
57
  return date.toISOString();
46
58
  }
47
59
  function buildDefaults(filePath, stats) {
48
- const basename = path.basename(filePath, path.extname(filePath));
60
+ const basename = path2.basename(filePath, path2.extname(filePath));
49
61
  const title = basename.replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
50
62
  return {
51
63
  title,
@@ -63,8 +75,8 @@ function splitFrontmatter(data, statsDefaults) {
63
75
  }
64
76
  }
65
77
  const title = typeof data.title === "string" && data.title.trim() ? data.title : statsDefaults.title;
66
- const priority = REQUIRED_PRIORITY.includes(data.priority) ? data.priority : statsDefaults.priority;
67
- const status = REQUIRED_STATUS.includes(data.status) ? data.status : statsDefaults.status;
78
+ const priority = isValidPriority(data.priority) ? data.priority : statsDefaults.priority;
79
+ const status = isValidStatus(data.status) ? data.status : statsDefaults.status;
68
80
  const createdAt = typeof data.createdAt === "string" && !Number.isNaN(Date.parse(data.createdAt)) ? new Date(data.createdAt).toISOString() : statsDefaults.createdAt;
69
81
  const updatedAt = typeof data.updatedAt === "string" && !Number.isNaN(Date.parse(data.updatedAt)) ? new Date(data.updatedAt).toISOString() : statsDefaults.updatedAt;
70
82
  const normalized = title !== data.title || priority !== data.priority || status !== data.status || createdAt !== data.createdAt || updatedAt !== data.updatedAt;
@@ -85,53 +97,8 @@ function serializeTask(record) {
85
97
  };
86
98
  return matter.stringify(record.content, data);
87
99
  }
88
- async function readDirectoryRecursive(rootDir, currentDir, results) {
89
- const entries = await fs.readdir(currentDir, { withFileTypes: true });
90
- for (const entry of entries) {
91
- if (entry.name === ".git" || entry.name === "node_modules") {
92
- continue;
93
- }
94
- const absolutePath = path.join(currentDir, entry.name);
95
- if (entry.isDirectory()) {
96
- await readDirectoryRecursive(rootDir, absolutePath, results);
97
- continue;
98
- }
99
- if (entry.name === CONFIG_FILE_NAME) {
100
- continue;
101
- }
102
- if (!MARKDOWN_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
103
- continue;
104
- }
105
- results.push(toPosixPath(path.relative(rootDir, absolutePath)));
106
- }
107
- }
108
- async function listMarkdownFiles(rootDir, taskDirs, ignorePaths) {
109
- const results = [];
110
- const seen = /* @__PURE__ */ new Set();
111
- const isIgnored = ignorePaths.length > 0 ? picomatch(ignorePaths) : null;
112
- for (const taskDir of taskDirs) {
113
- const scanDir = path.resolve(rootDir, taskDir);
114
- try {
115
- await fs.access(scanDir);
116
- } catch {
117
- continue;
118
- }
119
- const dirResults = [];
120
- await readDirectoryRecursive(rootDir, scanDir, dirResults);
121
- for (const filePath of dirResults) {
122
- if (!seen.has(filePath)) {
123
- seen.add(filePath);
124
- if (isIgnored && isIgnored(filePath)) {
125
- continue;
126
- }
127
- results.push(filePath);
128
- }
129
- }
130
- }
131
- return results.sort();
132
- }
133
100
  async function parseTask(rootDir, relativePath) {
134
- const absolutePath = path.join(rootDir, relativePath);
101
+ const absolutePath = path2.join(rootDir, relativePath);
135
102
  const raw = await fs.readFile(absolutePath, "utf8");
136
103
  const stats = await fs.stat(absolutePath);
137
104
  const parsed = matter(raw);
@@ -146,10 +113,19 @@ async function parseTask(rootDir, relativePath) {
146
113
  normalized
147
114
  };
148
115
  }
116
+
117
+ // src/taskStore/config.ts
118
+ import path3 from "path";
119
+ import { promises as fs2 } from "fs";
120
+
121
+ // src/types.ts
122
+ var CONFIG_FILE_NAME = ".md-task-viewer.json";
123
+
124
+ // src/taskStore/config.ts
149
125
  async function readConfig(rootDir) {
150
- const configFilePath = path.join(rootDir, CONFIG_FILE_NAME);
126
+ const configFilePath = path3.join(rootDir, CONFIG_FILE_NAME);
151
127
  try {
152
- const raw = await fs.readFile(configFilePath, "utf8");
128
+ const raw = await fs2.readFile(configFilePath, "utf8");
153
129
  const parsed = JSON.parse(raw);
154
130
  const taskDirs = Array.isArray(parsed.taskDirs) ? parsed.taskDirs.filter((item) => typeof item === "string") : ["."];
155
131
  const ignorePaths = Array.isArray(parsed.ignorePaths) ? parsed.ignorePaths.filter((item) => typeof item === "string") : [];
@@ -194,8 +170,14 @@ async function saveOrder(rootDir, order) {
194
170
  )
195
171
  );
196
172
  const existing = await readConfig(rootDir);
197
- const payload = { version: 1, taskDirs: existing.taskDirs, ignorePaths: existing.ignorePaths, order: normalized, commands: existing.commands };
198
- await fs.writeFile(path.join(rootDir, CONFIG_FILE_NAME), `${JSON.stringify(payload, null, 2)}
173
+ const payload = {
174
+ version: 1,
175
+ taskDirs: existing.taskDirs,
176
+ ignorePaths: existing.ignorePaths,
177
+ order: normalized,
178
+ commands: existing.commands
179
+ };
180
+ await fs2.writeFile(path3.join(rootDir, CONFIG_FILE_NAME), `${JSON.stringify(payload, null, 2)}
199
181
  `, "utf8");
200
182
  }
201
183
  async function saveConfig(rootDir, taskDirs, ignorePaths, commands) {
@@ -212,11 +194,102 @@ async function saveConfig(rootDir, taskDirs, ignorePaths, commands) {
212
194
  const existing = await readConfig(rootDir);
213
195
  const validatedIgnorePaths = ignorePaths ?? existing.ignorePaths;
214
196
  const validatedCommands = commands !== void 0 ? commands : existing.commands;
215
- const payload = { version: 1, taskDirs: validated, ignorePaths: validatedIgnorePaths, order: existing.order, commands: validatedCommands };
216
- await fs.writeFile(path.join(rootDir, CONFIG_FILE_NAME), `${JSON.stringify(payload, null, 2)}
197
+ const payload = {
198
+ version: 1,
199
+ taskDirs: validated,
200
+ ignorePaths: validatedIgnorePaths,
201
+ order: existing.order,
202
+ commands: validatedCommands
203
+ };
204
+ await fs2.writeFile(path3.join(rootDir, CONFIG_FILE_NAME), `${JSON.stringify(payload, null, 2)}
217
205
  `, "utf8");
218
206
  return payload;
219
207
  }
208
+ function parseOrderPayload(input) {
209
+ if (!Array.isArray(input)) {
210
+ throw new ValidationError("Order payload must be an array.");
211
+ }
212
+ return input.map((item) => ensureMarkdownExtension(normalizeRelativePath(String(item))));
213
+ }
214
+
215
+ // src/taskStore/tasks.ts
216
+ import path5 from "path";
217
+ import { promises as fs4 } from "fs";
218
+
219
+ // src/taskStore/scanner.ts
220
+ import picomatch from "picomatch";
221
+ import path4 from "path";
222
+ import { promises as fs3 } from "fs";
223
+ var MARKDOWN_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown"]);
224
+ async function readDirectoryRecursive(rootDir, currentDir, results) {
225
+ const entries = await fs3.readdir(currentDir, { withFileTypes: true });
226
+ for (const entry of entries) {
227
+ if (entry.name === ".git" || entry.name === "node_modules") {
228
+ continue;
229
+ }
230
+ const absolutePath = path4.join(currentDir, entry.name);
231
+ if (entry.isDirectory()) {
232
+ await readDirectoryRecursive(rootDir, absolutePath, results);
233
+ continue;
234
+ }
235
+ if (entry.name === CONFIG_FILE_NAME) {
236
+ continue;
237
+ }
238
+ if (!MARKDOWN_EXTENSIONS.has(path4.extname(entry.name).toLowerCase())) {
239
+ continue;
240
+ }
241
+ results.push(toPosixPath(path4.relative(rootDir, absolutePath)));
242
+ }
243
+ }
244
+ async function listMarkdownFiles(rootDir, taskDirs, ignorePaths) {
245
+ const results = [];
246
+ const seen = /* @__PURE__ */ new Set();
247
+ const isIgnored = ignorePaths.length > 0 ? picomatch(ignorePaths) : null;
248
+ for (const taskDir of taskDirs) {
249
+ const scanDir = path4.resolve(rootDir, taskDir);
250
+ try {
251
+ await fs3.access(scanDir);
252
+ } catch {
253
+ continue;
254
+ }
255
+ const dirResults = [];
256
+ await readDirectoryRecursive(rootDir, scanDir, dirResults);
257
+ for (const filePath of dirResults) {
258
+ if (!seen.has(filePath)) {
259
+ seen.add(filePath);
260
+ if (isIgnored && isIgnored(filePath)) {
261
+ continue;
262
+ }
263
+ results.push(filePath);
264
+ }
265
+ }
266
+ }
267
+ return results.sort();
268
+ }
269
+
270
+ // src/taskStore/tasks.ts
271
+ async function ensureDirectoryForFile(rootDir, relativeFilePath) {
272
+ const normalized = ensureMarkdownExtension(normalizeRelativePath(relativeFilePath));
273
+ const absolutePath = path5.join(rootDir, normalized);
274
+ const directory = path5.dirname(absolutePath);
275
+ await fs4.mkdir(directory, { recursive: true });
276
+ return normalized;
277
+ }
278
+ async function nextAvailablePath(rootDir, directory, title) {
279
+ const safeDirectory = directory ? normalizeRelativePath(directory) : "";
280
+ const slug = slugify(title);
281
+ const base = safeDirectory ? `${safeDirectory}/${slug}` : slug;
282
+ let attempt = 0;
283
+ while (true) {
284
+ const candidate = ensureMarkdownExtension(attempt === 0 ? base : `${base}-${attempt + 1}`);
285
+ try {
286
+ await fs4.access(path5.join(rootDir, candidate));
287
+ attempt += 1;
288
+ } catch {
289
+ return candidate;
290
+ }
291
+ }
292
+ }
220
293
  async function listTasks(rootDir) {
221
294
  const config = await readConfig(rootDir);
222
295
  const files = await listMarkdownFiles(rootDir, config.taskDirs, config.ignorePaths);
@@ -250,46 +323,27 @@ async function listTasks(rootDir) {
250
323
  });
251
324
  return { tasks: taskRecords, errors };
252
325
  }
253
- async function ensureDirectoryForFile(rootDir, relativeFilePath) {
254
- const normalized = ensureMarkdownExtension(normalizeRelativePath(relativeFilePath));
255
- const absolutePath = path.join(rootDir, normalized);
256
- const directory = path.dirname(absolutePath);
257
- await fs.mkdir(directory, { recursive: true });
258
- return normalized;
259
- }
260
- async function nextAvailablePath(rootDir, directory, title) {
261
- const safeDirectory = directory ? normalizeRelativePath(directory) : "";
262
- const slug = slugify(title);
263
- const base = safeDirectory ? `${safeDirectory}/${slug}` : slug;
264
- let attempt = 0;
265
- while (true) {
266
- const candidate = ensureMarkdownExtension(attempt === 0 ? base : `${base}-${attempt + 1}`);
267
- try {
268
- await fs.access(path.join(rootDir, candidate));
269
- attempt += 1;
270
- } catch {
271
- return candidate;
272
- }
273
- }
274
- }
275
326
  async function createTask(rootDir, input) {
276
327
  if (!input.title.trim()) {
277
328
  throw new ValidationError("Title is required.");
278
329
  }
330
+ const status = input.status !== void 0 ? ensureRequiredStatus(input.status) : "TODO";
279
331
  const now = asUtcISOString(/* @__PURE__ */ new Date());
280
332
  const relativePath = input.path?.trim() ? await ensureDirectoryForFile(rootDir, input.path) : await nextAvailablePath(rootDir, input.directory ?? "", input.title);
281
- const absolutePath = path.join(rootDir, relativePath);
333
+ const absolutePath = path5.join(rootDir, relativePath);
334
+ let targetExists = false;
282
335
  try {
283
- await fs.access(absolutePath);
336
+ await fs4.access(absolutePath);
337
+ targetExists = true;
284
338
  } catch (error) {
285
339
  const maybeError = error;
286
- if (maybeError.code === "ENOENT") {
287
- } else if (maybeError.code) {
340
+ if (maybeError.code !== "ENOENT") {
288
341
  throw error;
289
- } else {
290
- throw new ValidationError("A task already exists at that path.");
291
342
  }
292
343
  }
344
+ if (targetExists) {
345
+ throw new ValidationError("A task already exists at that path.");
346
+ }
293
347
  const record = {
294
348
  path: relativePath,
295
349
  content: input.content ?? "",
@@ -299,20 +353,21 @@ async function createTask(rootDir, input) {
299
353
  frontmatter: {
300
354
  title: input.title.trim(),
301
355
  priority: input.priority ?? "MUST",
302
- status: input.status ?? "TODO",
356
+ status,
303
357
  createdAt: now,
304
358
  updatedAt: now
305
359
  }
306
360
  };
307
- await fs.mkdir(path.dirname(absolutePath), { recursive: true });
308
- await fs.writeFile(absolutePath, serializeTask(record), "utf8");
309
- const current = await listTasks(rootDir);
310
- await saveOrder(rootDir, current.tasks.map((task) => task.path).concat(relativePath));
361
+ await fs4.mkdir(path5.dirname(absolutePath), { recursive: true });
362
+ await fs4.writeFile(absolutePath, serializeTask(record), "utf8");
363
+ const config = await readConfig(rootDir);
364
+ const filteredOrder = config.order.filter((item) => item !== relativePath);
365
+ await saveOrder(rootDir, [relativePath, ...filteredOrder]);
311
366
  return parseTask(rootDir, relativePath);
312
367
  }
313
368
  async function updateTask(rootDir, currentPath, input) {
314
369
  const normalizedCurrentPath = ensureMarkdownExtension(normalizeRelativePath(currentPath));
315
- const absoluteCurrentPath = path.join(rootDir, normalizedCurrentPath);
370
+ const absoluteCurrentPath = path5.join(rootDir, normalizedCurrentPath);
316
371
  let existing;
317
372
  try {
318
373
  existing = await parseTask(rootDir, normalizedCurrentPath);
@@ -326,20 +381,23 @@ async function updateTask(rootDir, currentPath, input) {
326
381
  if (input.baseUpdatedAt && existing.frontmatter.updatedAt !== input.baseUpdatedAt) {
327
382
  throw new ConflictError("The task changed on disk. Reload before saving.");
328
383
  }
384
+ const status = ensureRequiredStatus(input.status);
329
385
  const nextPath = input.path?.trim() ? await ensureDirectoryForFile(rootDir, input.path) : normalizedCurrentPath;
330
- const absoluteNextPath = path.join(rootDir, nextPath);
386
+ const absoluteNextPath = path5.join(rootDir, nextPath);
331
387
  if (nextPath !== normalizedCurrentPath) {
388
+ let targetExists = false;
332
389
  try {
333
- await fs.access(absoluteNextPath);
390
+ await fs4.access(absoluteNextPath);
391
+ targetExists = true;
334
392
  } catch (error) {
335
393
  const maybeError = error;
336
- if (maybeError.code === "ENOENT") {
337
- } else if (maybeError.code) {
394
+ if (maybeError.code !== "ENOENT") {
338
395
  throw error;
339
- } else {
340
- throw new ValidationError("A task already exists at the target path.");
341
396
  }
342
397
  }
398
+ if (targetExists) {
399
+ throw new ValidationError("A task already exists at the target path.");
400
+ }
343
401
  }
344
402
  const record = {
345
403
  path: nextPath,
@@ -350,15 +408,15 @@ async function updateTask(rootDir, currentPath, input) {
350
408
  frontmatter: {
351
409
  title: input.title.trim(),
352
410
  priority: input.priority,
353
- status: input.status,
411
+ status,
354
412
  createdAt: existing.frontmatter.createdAt,
355
413
  updatedAt: asUtcISOString(/* @__PURE__ */ new Date())
356
414
  }
357
415
  };
358
- await fs.writeFile(absoluteCurrentPath, serializeTask(record), "utf8");
416
+ await fs4.writeFile(absoluteCurrentPath, serializeTask(record), "utf8");
359
417
  if (nextPath !== normalizedCurrentPath) {
360
- await fs.mkdir(path.dirname(absoluteNextPath), { recursive: true });
361
- await fs.rename(absoluteCurrentPath, absoluteNextPath);
418
+ await fs4.mkdir(path5.dirname(absoluteNextPath), { recursive: true });
419
+ await fs4.rename(absoluteCurrentPath, absoluteNextPath);
362
420
  }
363
421
  if (nextPath !== normalizedCurrentPath) {
364
422
  const config = await readConfig(rootDir);
@@ -377,9 +435,9 @@ async function updateTask(rootDir, currentPath, input) {
377
435
  }
378
436
  async function deleteTask(rootDir, relativePath) {
379
437
  const normalizedPath = ensureMarkdownExtension(normalizeRelativePath(relativePath));
380
- const absolutePath = path.join(rootDir, normalizedPath);
438
+ const absolutePath = path5.join(rootDir, normalizedPath);
381
439
  try {
382
- await fs.unlink(absolutePath);
440
+ await fs4.unlink(absolutePath);
383
441
  } catch (error) {
384
442
  const maybeError = error;
385
443
  if (maybeError.code === "ENOENT") {
@@ -395,7 +453,7 @@ async function deleteTask(rootDir, relativePath) {
395
453
  }
396
454
  async function patchTaskFields(rootDir, currentPath, input) {
397
455
  const normalizedCurrentPath = ensureMarkdownExtension(normalizeRelativePath(currentPath));
398
- const absoluteCurrentPath = path.join(rootDir, normalizedCurrentPath);
456
+ const absoluteCurrentPath = path5.join(rootDir, normalizedCurrentPath);
399
457
  let existing;
400
458
  try {
401
459
  existing = await parseTask(rootDir, normalizedCurrentPath);
@@ -406,8 +464,8 @@ async function patchTaskFields(rootDir, currentPath, input) {
406
464
  }
407
465
  throw error;
408
466
  }
409
- const priority = input.priority && REQUIRED_PRIORITY.includes(input.priority) ? input.priority : existing.frontmatter.priority;
410
- const status = input.status && REQUIRED_STATUS.includes(input.status) ? input.status : existing.frontmatter.status;
467
+ const priority = input.priority && isValidPriority(input.priority) ? input.priority : existing.frontmatter.priority;
468
+ const status = input.status && isValidStatus(input.status) ? input.status : existing.frontmatter.status;
411
469
  if (priority === existing.frontmatter.priority && status === existing.frontmatter.status) {
412
470
  return existing;
413
471
  }
@@ -424,19 +482,13 @@ async function patchTaskFields(rootDir, currentPath, input) {
424
482
  updatedAt: asUtcISOString(/* @__PURE__ */ new Date())
425
483
  }
426
484
  };
427
- await fs.writeFile(absoluteCurrentPath, serializeTask(record), "utf8");
485
+ await fs4.writeFile(absoluteCurrentPath, serializeTask(record), "utf8");
428
486
  return parseTask(rootDir, normalizedCurrentPath);
429
487
  }
430
- function parseOrderPayload(input) {
431
- if (!Array.isArray(input)) {
432
- throw new ValidationError("Order payload must be an array.");
433
- }
434
- return input.map((item) => ensureMarkdownExtension(normalizeRelativePath(String(item))));
435
- }
436
488
 
437
489
  // src/commandExecutor.ts
438
490
  import { spawn } from "child_process";
439
- import path2 from "path";
491
+ import path6 from "path";
440
492
  var TIMEOUT_MS = 3e4;
441
493
  var VARIABLE_PATTERN = /\$\{?(TASK_TITLE|TASK_FILEPATH|TASK_BODY)\}?/g;
442
494
  function substituteVariables(command, vars) {
@@ -446,7 +498,7 @@ async function executeCommandPipeline(rootDir, steps, task) {
446
498
  if (steps.length === 0) {
447
499
  return { stdout: "", stderr: "", exitCode: 0, duration: 0 };
448
500
  }
449
- const absoluteFilePath = path2.resolve(rootDir, task.path);
501
+ const absoluteFilePath = path6.resolve(rootDir, task.path);
450
502
  const vars = {
451
503
  TASK_TITLE: task.frontmatter.title,
452
504
  TASK_FILEPATH: absoluteFilePath,
@@ -524,7 +576,7 @@ async function executeCommandPipeline(rootDir, steps, task) {
524
576
 
525
577
  // src/server.ts
526
578
  var __filename = fileURLToPath(import.meta.url);
527
- var __dirname = path3.dirname(__filename);
579
+ var __dirname = path7.dirname(__filename);
528
580
  function resolveClientDir(explicitClientDir) {
529
581
  if (explicitClientDir === null) {
530
582
  return null;
@@ -532,7 +584,7 @@ function resolveClientDir(explicitClientDir) {
532
584
  if (explicitClientDir) {
533
585
  return explicitClientDir;
534
586
  }
535
- return path3.resolve(__dirname, "client");
587
+ return path7.resolve(__dirname, "client");
536
588
  }
537
589
  function sendJsonError(reply, error) {
538
590
  if (error instanceof ValidationError) {
@@ -546,7 +598,7 @@ function sendJsonError(reply, error) {
546
598
  reply.code(500).send({ error: error instanceof Error ? error.message : "Internal server error" });
547
599
  }
548
600
  async function createServer(options) {
549
- const app = Fastify({ logger: false });
601
+ const app = Fastify({ logger: false, forceCloseConnections: true });
550
602
  const listeners = /* @__PURE__ */ new Set();
551
603
  const clientDir = resolveClientDir(options.clientDir);
552
604
  app.addHook("onClose", async () => {
@@ -695,18 +747,18 @@ async function createServer(options) {
695
747
  });
696
748
  const watcher = chokidar.watch(options.rootDir, {
697
749
  ignoreInitial: true,
698
- ignored: (watchPath) => watchPath.includes(`${path3.sep}.git`) || watchPath.includes(`${path3.sep}node_modules`)
750
+ ignored: (watchPath) => watchPath.includes(`${path7.sep}.git`) || watchPath.includes(`${path7.sep}node_modules`)
699
751
  });
700
752
  watcher.on("all", (eventName, changedPath) => {
701
753
  const isMarkdown = changedPath.endsWith(".md") || changedPath.endsWith(".markdown");
702
- const isConfigFile = path3.basename(changedPath) === ".md-task-viewer.json";
754
+ const isConfigFile = path7.basename(changedPath) === ".md-task-viewer.json";
703
755
  if (!isMarkdown && !isConfigFile) {
704
756
  return;
705
757
  }
706
758
  const payload = JSON.stringify({
707
759
  type: "tasks-changed",
708
760
  eventName,
709
- path: path3.relative(options.rootDir, changedPath)
761
+ path: path7.relative(options.rootDir, changedPath)
710
762
  });
711
763
  for (const listener of listeners) {
712
764
  listener.send(payload);