md-task-viewer 0.1.9 → 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.
- package/README.md +5 -3
- package/dist/cli.js +171 -124
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/index-CSNRoovR.css +1 -0
- package/dist/client/assets/index-j5ntXWRS.js +78 -0
- package/dist/client/index.html +2 -2
- package/dist/server.js +169 -122
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/index-CczpSlHS.js +0 -77
- package/dist/client/assets/index-u2_Kc4rH.css +0 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Each Markdown file (`1 file = 1 task`) is managed through a browser UI, and all
|
|
|
8
8
|
|
|
9
9
|
- List Markdown tasks
|
|
10
10
|
- Create, edit, and delete tasks
|
|
11
|
-
- Frontmatter-based `MUST` / `WANT` priority and `TODO` / `
|
|
11
|
+
- Frontmatter-based `MUST` / `WANT` priority and `TODO` / `DONE` status
|
|
12
12
|
- Drag-and-drop reordering
|
|
13
13
|
- Persistent ordering via a dedicated metadata file
|
|
14
14
|
- Auto-reload on external file changes
|
|
@@ -46,7 +46,7 @@ Each Markdown file should have frontmatter with the following keys:
|
|
|
46
46
|
---
|
|
47
47
|
title: Release notes
|
|
48
48
|
priority: MUST
|
|
49
|
-
status:
|
|
49
|
+
status: TODO
|
|
50
50
|
createdAt: 2026-03-15T08:00:00.000Z
|
|
51
51
|
updatedAt: 2026-03-15T09:30:00.000Z
|
|
52
52
|
---
|
|
@@ -60,7 +60,7 @@ Free-form body text.
|
|
|
60
60
|
|
|
61
61
|
- `title`
|
|
62
62
|
- `priority`: `MUST` or `WANT`
|
|
63
|
-
- `status`: `TODO
|
|
63
|
+
- `status`: `TODO` or `DONE`
|
|
64
64
|
- `createdAt`: UTC ISO 8601
|
|
65
65
|
- `updatedAt`: UTC ISO 8601
|
|
66
66
|
|
|
@@ -68,6 +68,8 @@ Unknown frontmatter keys are preserved as-is.
|
|
|
68
68
|
|
|
69
69
|
Files missing required keys are displayed with default values and normalized on save.
|
|
70
70
|
|
|
71
|
+
Legacy `status: WIP` is treated as `TODO` when loaded and will be replaced with `TODO` on save.
|
|
72
|
+
|
|
71
73
|
Files with unparseable YAML frontmatter are excluded from the list and shown in the error panel.
|
|
72
74
|
|
|
73
75
|
## Ordering Metadata
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import path8 from "path";
|
|
5
5
|
import process from "process";
|
|
6
6
|
import open from "open";
|
|
7
7
|
|
|
@@ -9,32 +9,25 @@ import open from "open";
|
|
|
9
9
|
import Fastify from "fastify";
|
|
10
10
|
import fastifyStatic from "@fastify/static";
|
|
11
11
|
import chokidar from "chokidar";
|
|
12
|
-
import
|
|
12
|
+
import path7 from "path";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
14
|
|
|
15
|
-
// src/taskStore.ts
|
|
16
|
-
import matter from "gray-matter";
|
|
17
|
-
import picomatch from "picomatch";
|
|
18
|
-
import path from "path";
|
|
19
|
-
import { promises as fs } from "fs";
|
|
20
|
-
|
|
21
|
-
// src/types.ts
|
|
22
|
-
var CONFIG_FILE_NAME = ".md-task-viewer.json";
|
|
23
|
-
|
|
24
15
|
// src/slugify.ts
|
|
25
16
|
function slugify(value) {
|
|
26
17
|
const slug = value.normalize("NFC").replace(/[\s\u3000]+/g, "-").replace(/[^\p{L}\p{N}-]+/gu, "").replace(/^-+|-+$/g, "");
|
|
27
18
|
return slug || "untitled-task";
|
|
28
19
|
}
|
|
29
20
|
|
|
30
|
-
// src/taskStore.ts
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
// src/taskStore/paths.ts
|
|
22
|
+
import path from "path";
|
|
23
|
+
|
|
24
|
+
// src/taskStore/errors.ts
|
|
34
25
|
var ConflictError = class extends Error {
|
|
35
26
|
};
|
|
36
27
|
var ValidationError = class extends Error {
|
|
37
28
|
};
|
|
29
|
+
|
|
30
|
+
// src/taskStore/paths.ts
|
|
38
31
|
function toPosixPath(filePath) {
|
|
39
32
|
return filePath.split(path.sep).join("/");
|
|
40
33
|
}
|
|
@@ -48,11 +41,30 @@ function normalizeRelativePath(candidate) {
|
|
|
48
41
|
function ensureMarkdownExtension(filePath) {
|
|
49
42
|
return path.posix.extname(filePath) ? filePath : `${filePath}.md`;
|
|
50
43
|
}
|
|
44
|
+
|
|
45
|
+
// src/taskStore/frontmatter.ts
|
|
46
|
+
import matter from "gray-matter";
|
|
47
|
+
import path2 from "path";
|
|
48
|
+
import { promises as fs } from "fs";
|
|
49
|
+
var REQUIRED_PRIORITY = ["MUST", "WANT"];
|
|
50
|
+
var REQUIRED_STATUS = ["TODO", "DONE"];
|
|
51
|
+
function isValidPriority(value) {
|
|
52
|
+
return REQUIRED_PRIORITY.includes(value);
|
|
53
|
+
}
|
|
54
|
+
function isValidStatus(value) {
|
|
55
|
+
return REQUIRED_STATUS.includes(value);
|
|
56
|
+
}
|
|
57
|
+
function ensureRequiredStatus(status) {
|
|
58
|
+
if (!isValidStatus(status)) {
|
|
59
|
+
throw new ValidationError("Status must be TODO or DONE.");
|
|
60
|
+
}
|
|
61
|
+
return status;
|
|
62
|
+
}
|
|
51
63
|
function asUtcISOString(date) {
|
|
52
64
|
return date.toISOString();
|
|
53
65
|
}
|
|
54
66
|
function buildDefaults(filePath, stats) {
|
|
55
|
-
const basename =
|
|
67
|
+
const basename = path2.basename(filePath, path2.extname(filePath));
|
|
56
68
|
const title = basename.replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
57
69
|
return {
|
|
58
70
|
title,
|
|
@@ -70,8 +82,8 @@ function splitFrontmatter(data, statsDefaults) {
|
|
|
70
82
|
}
|
|
71
83
|
}
|
|
72
84
|
const title = typeof data.title === "string" && data.title.trim() ? data.title : statsDefaults.title;
|
|
73
|
-
const priority =
|
|
74
|
-
const status =
|
|
85
|
+
const priority = isValidPriority(data.priority) ? data.priority : statsDefaults.priority;
|
|
86
|
+
const status = isValidStatus(data.status) ? data.status : statsDefaults.status;
|
|
75
87
|
const createdAt = typeof data.createdAt === "string" && !Number.isNaN(Date.parse(data.createdAt)) ? new Date(data.createdAt).toISOString() : statsDefaults.createdAt;
|
|
76
88
|
const updatedAt = typeof data.updatedAt === "string" && !Number.isNaN(Date.parse(data.updatedAt)) ? new Date(data.updatedAt).toISOString() : statsDefaults.updatedAt;
|
|
77
89
|
const normalized = title !== data.title || priority !== data.priority || status !== data.status || createdAt !== data.createdAt || updatedAt !== data.updatedAt;
|
|
@@ -92,53 +104,8 @@ function serializeTask(record) {
|
|
|
92
104
|
};
|
|
93
105
|
return matter.stringify(record.content, data);
|
|
94
106
|
}
|
|
95
|
-
async function readDirectoryRecursive(rootDir, currentDir, results) {
|
|
96
|
-
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
97
|
-
for (const entry of entries) {
|
|
98
|
-
if (entry.name === ".git" || entry.name === "node_modules") {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
const absolutePath = path.join(currentDir, entry.name);
|
|
102
|
-
if (entry.isDirectory()) {
|
|
103
|
-
await readDirectoryRecursive(rootDir, absolutePath, results);
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
if (entry.name === CONFIG_FILE_NAME) {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (!MARKDOWN_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
results.push(toPosixPath(path.relative(rootDir, absolutePath)));
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
async function listMarkdownFiles(rootDir, taskDirs, ignorePaths) {
|
|
116
|
-
const results = [];
|
|
117
|
-
const seen = /* @__PURE__ */ new Set();
|
|
118
|
-
const isIgnored = ignorePaths.length > 0 ? picomatch(ignorePaths) : null;
|
|
119
|
-
for (const taskDir of taskDirs) {
|
|
120
|
-
const scanDir = path.resolve(rootDir, taskDir);
|
|
121
|
-
try {
|
|
122
|
-
await fs.access(scanDir);
|
|
123
|
-
} catch {
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
const dirResults = [];
|
|
127
|
-
await readDirectoryRecursive(rootDir, scanDir, dirResults);
|
|
128
|
-
for (const filePath of dirResults) {
|
|
129
|
-
if (!seen.has(filePath)) {
|
|
130
|
-
seen.add(filePath);
|
|
131
|
-
if (isIgnored && isIgnored(filePath)) {
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
results.push(filePath);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return results.sort();
|
|
139
|
-
}
|
|
140
107
|
async function parseTask(rootDir, relativePath) {
|
|
141
|
-
const absolutePath =
|
|
108
|
+
const absolutePath = path2.join(rootDir, relativePath);
|
|
142
109
|
const raw = await fs.readFile(absolutePath, "utf8");
|
|
143
110
|
const stats = await fs.stat(absolutePath);
|
|
144
111
|
const parsed = matter(raw);
|
|
@@ -153,10 +120,19 @@ async function parseTask(rootDir, relativePath) {
|
|
|
153
120
|
normalized
|
|
154
121
|
};
|
|
155
122
|
}
|
|
123
|
+
|
|
124
|
+
// src/taskStore/config.ts
|
|
125
|
+
import path3 from "path";
|
|
126
|
+
import { promises as fs2 } from "fs";
|
|
127
|
+
|
|
128
|
+
// src/types.ts
|
|
129
|
+
var CONFIG_FILE_NAME = ".md-task-viewer.json";
|
|
130
|
+
|
|
131
|
+
// src/taskStore/config.ts
|
|
156
132
|
async function readConfig(rootDir) {
|
|
157
|
-
const configFilePath =
|
|
133
|
+
const configFilePath = path3.join(rootDir, CONFIG_FILE_NAME);
|
|
158
134
|
try {
|
|
159
|
-
const raw = await
|
|
135
|
+
const raw = await fs2.readFile(configFilePath, "utf8");
|
|
160
136
|
const parsed = JSON.parse(raw);
|
|
161
137
|
const taskDirs = Array.isArray(parsed.taskDirs) ? parsed.taskDirs.filter((item) => typeof item === "string") : ["."];
|
|
162
138
|
const ignorePaths = Array.isArray(parsed.ignorePaths) ? parsed.ignorePaths.filter((item) => typeof item === "string") : [];
|
|
@@ -201,8 +177,14 @@ async function saveOrder(rootDir, order) {
|
|
|
201
177
|
)
|
|
202
178
|
);
|
|
203
179
|
const existing = await readConfig(rootDir);
|
|
204
|
-
const payload = {
|
|
205
|
-
|
|
180
|
+
const payload = {
|
|
181
|
+
version: 1,
|
|
182
|
+
taskDirs: existing.taskDirs,
|
|
183
|
+
ignorePaths: existing.ignorePaths,
|
|
184
|
+
order: normalized,
|
|
185
|
+
commands: existing.commands
|
|
186
|
+
};
|
|
187
|
+
await fs2.writeFile(path3.join(rootDir, CONFIG_FILE_NAME), `${JSON.stringify(payload, null, 2)}
|
|
206
188
|
`, "utf8");
|
|
207
189
|
}
|
|
208
190
|
async function saveConfig(rootDir, taskDirs, ignorePaths, commands) {
|
|
@@ -219,11 +201,102 @@ async function saveConfig(rootDir, taskDirs, ignorePaths, commands) {
|
|
|
219
201
|
const existing = await readConfig(rootDir);
|
|
220
202
|
const validatedIgnorePaths = ignorePaths ?? existing.ignorePaths;
|
|
221
203
|
const validatedCommands = commands !== void 0 ? commands : existing.commands;
|
|
222
|
-
const payload = {
|
|
223
|
-
|
|
204
|
+
const payload = {
|
|
205
|
+
version: 1,
|
|
206
|
+
taskDirs: validated,
|
|
207
|
+
ignorePaths: validatedIgnorePaths,
|
|
208
|
+
order: existing.order,
|
|
209
|
+
commands: validatedCommands
|
|
210
|
+
};
|
|
211
|
+
await fs2.writeFile(path3.join(rootDir, CONFIG_FILE_NAME), `${JSON.stringify(payload, null, 2)}
|
|
224
212
|
`, "utf8");
|
|
225
213
|
return payload;
|
|
226
214
|
}
|
|
215
|
+
function parseOrderPayload(input) {
|
|
216
|
+
if (!Array.isArray(input)) {
|
|
217
|
+
throw new ValidationError("Order payload must be an array.");
|
|
218
|
+
}
|
|
219
|
+
return input.map((item) => ensureMarkdownExtension(normalizeRelativePath(String(item))));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/taskStore/tasks.ts
|
|
223
|
+
import path5 from "path";
|
|
224
|
+
import { promises as fs4 } from "fs";
|
|
225
|
+
|
|
226
|
+
// src/taskStore/scanner.ts
|
|
227
|
+
import picomatch from "picomatch";
|
|
228
|
+
import path4 from "path";
|
|
229
|
+
import { promises as fs3 } from "fs";
|
|
230
|
+
var MARKDOWN_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown"]);
|
|
231
|
+
async function readDirectoryRecursive(rootDir, currentDir, results) {
|
|
232
|
+
const entries = await fs3.readdir(currentDir, { withFileTypes: true });
|
|
233
|
+
for (const entry of entries) {
|
|
234
|
+
if (entry.name === ".git" || entry.name === "node_modules") {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const absolutePath = path4.join(currentDir, entry.name);
|
|
238
|
+
if (entry.isDirectory()) {
|
|
239
|
+
await readDirectoryRecursive(rootDir, absolutePath, results);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (entry.name === CONFIG_FILE_NAME) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (!MARKDOWN_EXTENSIONS.has(path4.extname(entry.name).toLowerCase())) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
results.push(toPosixPath(path4.relative(rootDir, absolutePath)));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function listMarkdownFiles(rootDir, taskDirs, ignorePaths) {
|
|
252
|
+
const results = [];
|
|
253
|
+
const seen = /* @__PURE__ */ new Set();
|
|
254
|
+
const isIgnored = ignorePaths.length > 0 ? picomatch(ignorePaths) : null;
|
|
255
|
+
for (const taskDir of taskDirs) {
|
|
256
|
+
const scanDir = path4.resolve(rootDir, taskDir);
|
|
257
|
+
try {
|
|
258
|
+
await fs3.access(scanDir);
|
|
259
|
+
} catch {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const dirResults = [];
|
|
263
|
+
await readDirectoryRecursive(rootDir, scanDir, dirResults);
|
|
264
|
+
for (const filePath of dirResults) {
|
|
265
|
+
if (!seen.has(filePath)) {
|
|
266
|
+
seen.add(filePath);
|
|
267
|
+
if (isIgnored && isIgnored(filePath)) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
results.push(filePath);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return results.sort();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/taskStore/tasks.ts
|
|
278
|
+
async function ensureDirectoryForFile(rootDir, relativeFilePath) {
|
|
279
|
+
const normalized = ensureMarkdownExtension(normalizeRelativePath(relativeFilePath));
|
|
280
|
+
const absolutePath = path5.join(rootDir, normalized);
|
|
281
|
+
const directory = path5.dirname(absolutePath);
|
|
282
|
+
await fs4.mkdir(directory, { recursive: true });
|
|
283
|
+
return normalized;
|
|
284
|
+
}
|
|
285
|
+
async function nextAvailablePath(rootDir, directory, title) {
|
|
286
|
+
const safeDirectory = directory ? normalizeRelativePath(directory) : "";
|
|
287
|
+
const slug = slugify(title);
|
|
288
|
+
const base = safeDirectory ? `${safeDirectory}/${slug}` : slug;
|
|
289
|
+
let attempt = 0;
|
|
290
|
+
while (true) {
|
|
291
|
+
const candidate = ensureMarkdownExtension(attempt === 0 ? base : `${base}-${attempt + 1}`);
|
|
292
|
+
try {
|
|
293
|
+
await fs4.access(path5.join(rootDir, candidate));
|
|
294
|
+
attempt += 1;
|
|
295
|
+
} catch {
|
|
296
|
+
return candidate;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
227
300
|
async function listTasks(rootDir) {
|
|
228
301
|
const config = await readConfig(rootDir);
|
|
229
302
|
const files = await listMarkdownFiles(rootDir, config.taskDirs, config.ignorePaths);
|
|
@@ -257,38 +330,17 @@ async function listTasks(rootDir) {
|
|
|
257
330
|
});
|
|
258
331
|
return { tasks: taskRecords, errors };
|
|
259
332
|
}
|
|
260
|
-
async function ensureDirectoryForFile(rootDir, relativeFilePath) {
|
|
261
|
-
const normalized = ensureMarkdownExtension(normalizeRelativePath(relativeFilePath));
|
|
262
|
-
const absolutePath = path.join(rootDir, normalized);
|
|
263
|
-
const directory = path.dirname(absolutePath);
|
|
264
|
-
await fs.mkdir(directory, { recursive: true });
|
|
265
|
-
return normalized;
|
|
266
|
-
}
|
|
267
|
-
async function nextAvailablePath(rootDir, directory, title) {
|
|
268
|
-
const safeDirectory = directory ? normalizeRelativePath(directory) : "";
|
|
269
|
-
const slug = slugify(title);
|
|
270
|
-
const base = safeDirectory ? `${safeDirectory}/${slug}` : slug;
|
|
271
|
-
let attempt = 0;
|
|
272
|
-
while (true) {
|
|
273
|
-
const candidate = ensureMarkdownExtension(attempt === 0 ? base : `${base}-${attempt + 1}`);
|
|
274
|
-
try {
|
|
275
|
-
await fs.access(path.join(rootDir, candidate));
|
|
276
|
-
attempt += 1;
|
|
277
|
-
} catch {
|
|
278
|
-
return candidate;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
333
|
async function createTask(rootDir, input) {
|
|
283
334
|
if (!input.title.trim()) {
|
|
284
335
|
throw new ValidationError("Title is required.");
|
|
285
336
|
}
|
|
337
|
+
const status = input.status !== void 0 ? ensureRequiredStatus(input.status) : "TODO";
|
|
286
338
|
const now = asUtcISOString(/* @__PURE__ */ new Date());
|
|
287
339
|
const relativePath = input.path?.trim() ? await ensureDirectoryForFile(rootDir, input.path) : await nextAvailablePath(rootDir, input.directory ?? "", input.title);
|
|
288
|
-
const absolutePath =
|
|
340
|
+
const absolutePath = path5.join(rootDir, relativePath);
|
|
289
341
|
let targetExists = false;
|
|
290
342
|
try {
|
|
291
|
-
await
|
|
343
|
+
await fs4.access(absolutePath);
|
|
292
344
|
targetExists = true;
|
|
293
345
|
} catch (error) {
|
|
294
346
|
const maybeError = error;
|
|
@@ -308,13 +360,13 @@ async function createTask(rootDir, input) {
|
|
|
308
360
|
frontmatter: {
|
|
309
361
|
title: input.title.trim(),
|
|
310
362
|
priority: input.priority ?? "MUST",
|
|
311
|
-
status
|
|
363
|
+
status,
|
|
312
364
|
createdAt: now,
|
|
313
365
|
updatedAt: now
|
|
314
366
|
}
|
|
315
367
|
};
|
|
316
|
-
await
|
|
317
|
-
await
|
|
368
|
+
await fs4.mkdir(path5.dirname(absolutePath), { recursive: true });
|
|
369
|
+
await fs4.writeFile(absolutePath, serializeTask(record), "utf8");
|
|
318
370
|
const config = await readConfig(rootDir);
|
|
319
371
|
const filteredOrder = config.order.filter((item) => item !== relativePath);
|
|
320
372
|
await saveOrder(rootDir, [relativePath, ...filteredOrder]);
|
|
@@ -322,7 +374,7 @@ async function createTask(rootDir, input) {
|
|
|
322
374
|
}
|
|
323
375
|
async function updateTask(rootDir, currentPath, input) {
|
|
324
376
|
const normalizedCurrentPath = ensureMarkdownExtension(normalizeRelativePath(currentPath));
|
|
325
|
-
const absoluteCurrentPath =
|
|
377
|
+
const absoluteCurrentPath = path5.join(rootDir, normalizedCurrentPath);
|
|
326
378
|
let existing;
|
|
327
379
|
try {
|
|
328
380
|
existing = await parseTask(rootDir, normalizedCurrentPath);
|
|
@@ -336,12 +388,13 @@ async function updateTask(rootDir, currentPath, input) {
|
|
|
336
388
|
if (input.baseUpdatedAt && existing.frontmatter.updatedAt !== input.baseUpdatedAt) {
|
|
337
389
|
throw new ConflictError("The task changed on disk. Reload before saving.");
|
|
338
390
|
}
|
|
391
|
+
const status = ensureRequiredStatus(input.status);
|
|
339
392
|
const nextPath = input.path?.trim() ? await ensureDirectoryForFile(rootDir, input.path) : normalizedCurrentPath;
|
|
340
|
-
const absoluteNextPath =
|
|
393
|
+
const absoluteNextPath = path5.join(rootDir, nextPath);
|
|
341
394
|
if (nextPath !== normalizedCurrentPath) {
|
|
342
395
|
let targetExists = false;
|
|
343
396
|
try {
|
|
344
|
-
await
|
|
397
|
+
await fs4.access(absoluteNextPath);
|
|
345
398
|
targetExists = true;
|
|
346
399
|
} catch (error) {
|
|
347
400
|
const maybeError = error;
|
|
@@ -362,15 +415,15 @@ async function updateTask(rootDir, currentPath, input) {
|
|
|
362
415
|
frontmatter: {
|
|
363
416
|
title: input.title.trim(),
|
|
364
417
|
priority: input.priority,
|
|
365
|
-
status
|
|
418
|
+
status,
|
|
366
419
|
createdAt: existing.frontmatter.createdAt,
|
|
367
420
|
updatedAt: asUtcISOString(/* @__PURE__ */ new Date())
|
|
368
421
|
}
|
|
369
422
|
};
|
|
370
|
-
await
|
|
423
|
+
await fs4.writeFile(absoluteCurrentPath, serializeTask(record), "utf8");
|
|
371
424
|
if (nextPath !== normalizedCurrentPath) {
|
|
372
|
-
await
|
|
373
|
-
await
|
|
425
|
+
await fs4.mkdir(path5.dirname(absoluteNextPath), { recursive: true });
|
|
426
|
+
await fs4.rename(absoluteCurrentPath, absoluteNextPath);
|
|
374
427
|
}
|
|
375
428
|
if (nextPath !== normalizedCurrentPath) {
|
|
376
429
|
const config = await readConfig(rootDir);
|
|
@@ -389,9 +442,9 @@ async function updateTask(rootDir, currentPath, input) {
|
|
|
389
442
|
}
|
|
390
443
|
async function deleteTask(rootDir, relativePath) {
|
|
391
444
|
const normalizedPath = ensureMarkdownExtension(normalizeRelativePath(relativePath));
|
|
392
|
-
const absolutePath =
|
|
445
|
+
const absolutePath = path5.join(rootDir, normalizedPath);
|
|
393
446
|
try {
|
|
394
|
-
await
|
|
447
|
+
await fs4.unlink(absolutePath);
|
|
395
448
|
} catch (error) {
|
|
396
449
|
const maybeError = error;
|
|
397
450
|
if (maybeError.code === "ENOENT") {
|
|
@@ -407,7 +460,7 @@ async function deleteTask(rootDir, relativePath) {
|
|
|
407
460
|
}
|
|
408
461
|
async function patchTaskFields(rootDir, currentPath, input) {
|
|
409
462
|
const normalizedCurrentPath = ensureMarkdownExtension(normalizeRelativePath(currentPath));
|
|
410
|
-
const absoluteCurrentPath =
|
|
463
|
+
const absoluteCurrentPath = path5.join(rootDir, normalizedCurrentPath);
|
|
411
464
|
let existing;
|
|
412
465
|
try {
|
|
413
466
|
existing = await parseTask(rootDir, normalizedCurrentPath);
|
|
@@ -418,8 +471,8 @@ async function patchTaskFields(rootDir, currentPath, input) {
|
|
|
418
471
|
}
|
|
419
472
|
throw error;
|
|
420
473
|
}
|
|
421
|
-
const priority = input.priority &&
|
|
422
|
-
const status = input.status &&
|
|
474
|
+
const priority = input.priority && isValidPriority(input.priority) ? input.priority : existing.frontmatter.priority;
|
|
475
|
+
const status = input.status && isValidStatus(input.status) ? input.status : existing.frontmatter.status;
|
|
423
476
|
if (priority === existing.frontmatter.priority && status === existing.frontmatter.status) {
|
|
424
477
|
return existing;
|
|
425
478
|
}
|
|
@@ -436,19 +489,13 @@ async function patchTaskFields(rootDir, currentPath, input) {
|
|
|
436
489
|
updatedAt: asUtcISOString(/* @__PURE__ */ new Date())
|
|
437
490
|
}
|
|
438
491
|
};
|
|
439
|
-
await
|
|
492
|
+
await fs4.writeFile(absoluteCurrentPath, serializeTask(record), "utf8");
|
|
440
493
|
return parseTask(rootDir, normalizedCurrentPath);
|
|
441
494
|
}
|
|
442
|
-
function parseOrderPayload(input) {
|
|
443
|
-
if (!Array.isArray(input)) {
|
|
444
|
-
throw new ValidationError("Order payload must be an array.");
|
|
445
|
-
}
|
|
446
|
-
return input.map((item) => ensureMarkdownExtension(normalizeRelativePath(String(item))));
|
|
447
|
-
}
|
|
448
495
|
|
|
449
496
|
// src/commandExecutor.ts
|
|
450
497
|
import { spawn } from "child_process";
|
|
451
|
-
import
|
|
498
|
+
import path6 from "path";
|
|
452
499
|
var TIMEOUT_MS = 3e4;
|
|
453
500
|
var VARIABLE_PATTERN = /\$\{?(TASK_TITLE|TASK_FILEPATH|TASK_BODY)\}?/g;
|
|
454
501
|
function substituteVariables(command, vars) {
|
|
@@ -458,7 +505,7 @@ async function executeCommandPipeline(rootDir, steps, task) {
|
|
|
458
505
|
if (steps.length === 0) {
|
|
459
506
|
return { stdout: "", stderr: "", exitCode: 0, duration: 0 };
|
|
460
507
|
}
|
|
461
|
-
const absoluteFilePath =
|
|
508
|
+
const absoluteFilePath = path6.resolve(rootDir, task.path);
|
|
462
509
|
const vars = {
|
|
463
510
|
TASK_TITLE: task.frontmatter.title,
|
|
464
511
|
TASK_FILEPATH: absoluteFilePath,
|
|
@@ -536,7 +583,7 @@ async function executeCommandPipeline(rootDir, steps, task) {
|
|
|
536
583
|
|
|
537
584
|
// src/server.ts
|
|
538
585
|
var __filename = fileURLToPath(import.meta.url);
|
|
539
|
-
var __dirname =
|
|
586
|
+
var __dirname = path7.dirname(__filename);
|
|
540
587
|
function resolveClientDir(explicitClientDir) {
|
|
541
588
|
if (explicitClientDir === null) {
|
|
542
589
|
return null;
|
|
@@ -544,7 +591,7 @@ function resolveClientDir(explicitClientDir) {
|
|
|
544
591
|
if (explicitClientDir) {
|
|
545
592
|
return explicitClientDir;
|
|
546
593
|
}
|
|
547
|
-
return
|
|
594
|
+
return path7.resolve(__dirname, "client");
|
|
548
595
|
}
|
|
549
596
|
function sendJsonError(reply, error) {
|
|
550
597
|
if (error instanceof ValidationError) {
|
|
@@ -707,18 +754,18 @@ async function createServer(options) {
|
|
|
707
754
|
});
|
|
708
755
|
const watcher = chokidar.watch(options.rootDir, {
|
|
709
756
|
ignoreInitial: true,
|
|
710
|
-
ignored: (watchPath) => watchPath.includes(`${
|
|
757
|
+
ignored: (watchPath) => watchPath.includes(`${path7.sep}.git`) || watchPath.includes(`${path7.sep}node_modules`)
|
|
711
758
|
});
|
|
712
759
|
watcher.on("all", (eventName, changedPath) => {
|
|
713
760
|
const isMarkdown = changedPath.endsWith(".md") || changedPath.endsWith(".markdown");
|
|
714
|
-
const isConfigFile =
|
|
761
|
+
const isConfigFile = path7.basename(changedPath) === ".md-task-viewer.json";
|
|
715
762
|
if (!isMarkdown && !isConfigFile) {
|
|
716
763
|
return;
|
|
717
764
|
}
|
|
718
765
|
const payload = JSON.stringify({
|
|
719
766
|
type: "tasks-changed",
|
|
720
767
|
eventName,
|
|
721
|
-
path:
|
|
768
|
+
path: path7.relative(options.rootDir, changedPath)
|
|
722
769
|
});
|
|
723
770
|
for (const listener of listeners) {
|
|
724
771
|
listener.send(payload);
|
|
@@ -765,7 +812,7 @@ function parseArgs(argv) {
|
|
|
765
812
|
continue;
|
|
766
813
|
}
|
|
767
814
|
if (!current.startsWith("--")) {
|
|
768
|
-
rootDir =
|
|
815
|
+
rootDir = path8.resolve(current);
|
|
769
816
|
}
|
|
770
817
|
}
|
|
771
818
|
return { rootDir, port, host, shouldOpen };
|