toolcraft 0.0.11 → 0.0.12

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.
Files changed (30) hide show
  1. package/dist/cli.js +274 -160
  2. package/dist/renderer.d.ts +8 -2
  3. package/dist/renderer.js +71 -12
  4. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +4 -0
  5. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +132 -0
  6. package/node_modules/@poe-code/design-system/dist/components/help-formatter.d.ts +13 -0
  7. package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +116 -7
  8. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +2 -2
  9. package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
  10. package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
  11. package/node_modules/@poe-code/design-system/dist/components/text.js +8 -0
  12. package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -2
  13. package/node_modules/@poe-code/design-system/dist/index.js +2 -1
  14. package/node_modules/@poe-code/task-list/README.md +49 -5
  15. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.d.ts +19 -0
  16. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +62 -0
  17. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +13 -0
  18. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +627 -0
  19. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +253 -41
  20. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +7 -1
  21. package/node_modules/@poe-code/task-list/dist/backends/utils.js +21 -0
  22. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +171 -16
  23. package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
  24. package/node_modules/@poe-code/task-list/dist/index.js +1 -1
  25. package/node_modules/@poe-code/task-list/dist/open.d.ts +4 -2
  26. package/node_modules/@poe-code/task-list/dist/open.js +27 -3
  27. package/node_modules/@poe-code/task-list/dist/types.d.ts +51 -3
  28. package/node_modules/@poe-code/task-list/dist/types.js +25 -0
  29. package/node_modules/@poe-code/task-list/package.json +1 -0
  30. package/package.json +3 -2
@@ -4,15 +4,18 @@ import { parseDocument, stringify } from "yaml";
4
4
  import taskSchema from "../schema/task.schema.json" with { type: "json" };
5
5
  import { eventsFromState, findEvent } from "../state-machine.js";
6
6
  import { resolveStateMachine } from "../state.js";
7
- import { InvalidTransitionError, MalformedTaskError, TaskAlreadyExistsError, TaskNotFoundError } from "../types.js";
8
- import { hasErrorCode, isRecord, sortStrings, sortTasks, statIfExists, validateTaskId, writeAtomically } from "./utils.js";
7
+ import { AnchorNotFoundError, InvalidTransitionError, MalformedTaskError, OrderMismatchError, TaskAlreadyExistsError, TaskNotFoundError } from "../types.js";
8
+ import { applyOrder, hasErrorCode, isRecord, sortStrings, statIfExists, validateTaskId, writeAtomically } from "./utils.js";
9
9
  const ARCHIVE_DIRECTORY_NAME = "archive";
10
10
  const MARKDOWN_EXTENSION = ".md";
11
11
  const TASK_KIND = "task";
12
12
  const TASK_VERSION = 1;
13
13
  const TASK_SCHEMA_ID = taskSchema.$id;
14
+ const ORDER_LOCK_FILENAME = ".order.lock";
15
+ const MIN_PREFIX_WIDTH = 2;
14
16
  const RESERVED_FRONTMATTER_KEYS = new Set([
15
17
  "$schema",
18
+ "created",
16
19
  "description",
17
20
  "kind",
18
21
  "name",
@@ -48,8 +51,8 @@ function listPath(rootPath, list) {
48
51
  function archiveDirectoryPath(rootPath, list) {
49
52
  return path.join(listPath(rootPath, list), ARCHIVE_DIRECTORY_NAME);
50
53
  }
51
- function activeTaskPath(rootPath, list, id) {
52
- return path.join(listPath(rootPath, list), `${id}${MARKDOWN_EXTENSION}`);
54
+ function activeTaskFilename(id, order, width) {
55
+ return `${String(order).padStart(width, "0")}-${id}${MARKDOWN_EXTENSION}`;
53
56
  }
54
57
  function archivedTaskPath(rootPath, list, id) {
55
58
  return path.join(archiveDirectoryPath(rootPath, list), `${id}${MARKDOWN_EXTENSION}`);
@@ -63,6 +66,28 @@ function isHiddenEntry(entryName) {
63
66
  function isLockFile(entryName) {
64
67
  return entryName.endsWith(".lock");
65
68
  }
69
+ function isValidTaskIdShape(id) {
70
+ return id.length > 0 && !id.startsWith(".") && !id.includes("/") && !id.includes("\\") && !id.includes("..");
71
+ }
72
+ function parseActiveFilename(entryName) {
73
+ if (!isMarkdownFile(entryName))
74
+ return undefined;
75
+ const stem = entryName.slice(0, -MARKDOWN_EXTENSION.length);
76
+ const match = /^(\d+)-(.+)$/.exec(stem);
77
+ if (match) {
78
+ const id = match[2];
79
+ if (isValidTaskIdShape(id)) {
80
+ return { id, order: Number.parseInt(match[1], 10) };
81
+ }
82
+ }
83
+ if (isValidTaskIdShape(stem)) {
84
+ return { id: stem, order: null };
85
+ }
86
+ return undefined;
87
+ }
88
+ function padWidthForCount(count) {
89
+ return Math.max(MIN_PREFIX_WIDTH, String(Math.max(count, 1)).length);
90
+ }
66
91
  function malformedTask(filePath, field) {
67
92
  return new MalformedTaskError(`Malformed task "${filePath}": invalid "${field}".`);
68
93
  }
@@ -180,22 +205,32 @@ async function readTaskFile(fs, list, id, filePath, validStates) {
180
205
  task: createTask(list, id, frontmatter, document.body)
181
206
  };
182
207
  }
208
+ async function findActiveTaskFilename(fs, listDirectoryPath, id) {
209
+ const entries = await readDirectoryNames(fs, listDirectoryPath);
210
+ for (const entryName of entries) {
211
+ if (isHiddenEntry(entryName) || isLockFile(entryName))
212
+ continue;
213
+ const parsed = parseActiveFilename(entryName);
214
+ if (parsed?.id === id) {
215
+ return entryName;
216
+ }
217
+ }
218
+ return undefined;
219
+ }
183
220
  async function findTaskLocation(fs, rootPath, list, id) {
184
- const activePath = activeTaskPath(rootPath, list, id);
185
- const activeStat = await statIfExists(fs, activePath);
186
- if (activeStat?.isFile()) {
187
- return {
188
- archived: false,
189
- path: activePath
190
- };
221
+ const listDirectoryPath = listPath(rootPath, list);
222
+ const activeName = await findActiveTaskFilename(fs, listDirectoryPath, id);
223
+ if (activeName) {
224
+ const activePath = path.join(listDirectoryPath, activeName);
225
+ const activeStat = await statIfExists(fs, activePath);
226
+ if (activeStat?.isFile()) {
227
+ return { archived: false, path: activePath };
228
+ }
191
229
  }
192
230
  const archivedPath = archivedTaskPath(rootPath, list, id);
193
231
  const archivedStat = await statIfExists(fs, archivedPath);
194
232
  if (archivedStat?.isFile()) {
195
- return {
196
- archived: true,
197
- path: archivedPath
198
- };
233
+ return { archived: true, path: archivedPath };
199
234
  }
200
235
  return undefined;
201
236
  }
@@ -224,6 +259,7 @@ function createdFrontmatter(defaults, input, initialState) {
224
259
  frontmatter[key] = value;
225
260
  }
226
261
  }
262
+ frontmatter.created = new Date().toISOString();
227
263
  return frontmatter;
228
264
  }
229
265
  function updatedFrontmatter(existingFrontmatter, task, patch) {
@@ -266,6 +302,11 @@ function assertCreateDoesNotSetState(input) {
266
302
  throw new Error('Tasks.create() does not accept "state"; new tasks always start at stateMachine.initial.');
267
303
  }
268
304
  }
305
+ function assertCreateHasId(input) {
306
+ if (input.id === undefined) {
307
+ throw new Error("id is required for markdown-dir backend");
308
+ }
309
+ }
269
310
  function assertUpdateDoesNotSetState(patch) {
270
311
  if (Object.prototype.hasOwnProperty.call(patch, "state")) {
271
312
  throw new Error('Tasks.update() does not accept "state"; use fire() to change task state.');
@@ -275,26 +316,86 @@ function createTasksView(deps, list) {
275
316
  const listDirectoryPath = listPath(deps.path, list);
276
317
  const stateMachine = resolveStateMachine(deps.stateMachine);
277
318
  const validStates = new Set(stateMachine.states);
278
- async function listFiles(directoryPath) {
279
- const entries = await readDirectoryNames(deps.fs, directoryPath);
280
- const tasks = [];
319
+ async function readActiveEntries() {
320
+ const entries = await readDirectoryNames(deps.fs, listDirectoryPath);
321
+ const result = [];
281
322
  for (const entryName of entries) {
282
- if (isHiddenEntry(entryName) || isLockFile(entryName) || !isMarkdownFile(entryName)) {
323
+ if (isHiddenEntry(entryName) || isLockFile(entryName))
283
324
  continue;
284
- }
285
- const entryPath = path.join(directoryPath, entryName);
325
+ const parsed = parseActiveFilename(entryName);
326
+ if (!parsed)
327
+ continue;
328
+ const entryPath = path.join(listDirectoryPath, entryName);
286
329
  const entryStat = await statIfExists(deps.fs, entryPath);
287
- if (!entryStat?.isFile()) {
330
+ if (!entryStat?.isFile())
331
+ continue;
332
+ result.push({ id: parsed.id, order: parsed.order, filename: entryName });
333
+ }
334
+ result.sort((left, right) => {
335
+ const leftOrder = left.order ?? Number.POSITIVE_INFINITY;
336
+ const rightOrder = right.order ?? Number.POSITIVE_INFINITY;
337
+ if (leftOrder !== rightOrder)
338
+ return leftOrder - rightOrder;
339
+ return left.filename.localeCompare(right.filename);
340
+ });
341
+ return result;
342
+ }
343
+ async function readActiveTasks() {
344
+ const entries = await readActiveEntries();
345
+ const tasks = new Map();
346
+ for (const entry of entries) {
347
+ const filePath = path.join(listDirectoryPath, entry.filename);
348
+ const file = await readTaskFile(deps.fs, list, entry.id, filePath, validStates);
349
+ tasks.set(entry.id, { task: file.task, raw: file.frontmatter });
350
+ }
351
+ return { entries, tasks };
352
+ }
353
+ async function readArchivedTasks() {
354
+ const archivePath = archiveDirectoryPath(deps.path, list);
355
+ const entries = await readDirectoryNames(deps.fs, archivePath);
356
+ const result = [];
357
+ for (const entryName of entries) {
358
+ if (isHiddenEntry(entryName) || isLockFile(entryName) || !isMarkdownFile(entryName))
359
+ continue;
360
+ const entryPath = path.join(archivePath, entryName);
361
+ const entryStat = await statIfExists(deps.fs, entryPath);
362
+ if (!entryStat?.isFile())
288
363
  continue;
289
- }
290
364
  const id = entryName.slice(0, -MARKDOWN_EXTENSION.length);
291
- tasks.push((await readTaskFile(deps.fs, list, id, entryPath, validStates)).task);
365
+ const file = await readTaskFile(deps.fs, list, id, entryPath, validStates);
366
+ result.push({ task: file.task, raw: file.frontmatter });
292
367
  }
293
- return sortTasks(tasks);
368
+ return result.sort((left, right) => left.task.qualifiedId.localeCompare(right.task.qualifiedId));
294
369
  }
295
- async function withTaskLock(id, action) {
296
- validateTaskId(id);
297
- const release = await acquireFileLock(activeTaskPath(deps.path, list, id), {
370
+ async function rewriteListPrefixes(orderedIds) {
371
+ const entries = await readActiveEntries();
372
+ const byId = new Map(entries.map((entry) => [entry.id, entry]));
373
+ const width = padWidthForCount(orderedIds.length);
374
+ for (let index = 0; index < orderedIds.length; index += 1) {
375
+ const id = orderedIds[index];
376
+ const entry = byId.get(id);
377
+ if (!entry)
378
+ continue;
379
+ const desiredFilename = activeTaskFilename(id, index + 1, width);
380
+ if (entry.filename !== desiredFilename) {
381
+ const fromPath = path.join(listDirectoryPath, entry.filename);
382
+ const stagingPath = path.join(listDirectoryPath, `${desiredFilename}.staging-${process.pid}-${index}`);
383
+ await deps.fs.rename(fromPath, stagingPath);
384
+ entry.filename = path.basename(stagingPath);
385
+ }
386
+ }
387
+ const refreshed = await readDirectoryNames(deps.fs, listDirectoryPath);
388
+ for (const entryName of refreshed) {
389
+ const stagingMatch = /\.staging-\d+-\d+$/.exec(entryName);
390
+ if (!stagingMatch)
391
+ continue;
392
+ const desiredName = entryName.slice(0, stagingMatch.index);
393
+ await deps.fs.rename(path.join(listDirectoryPath, entryName), path.join(listDirectoryPath, desiredName));
394
+ }
395
+ }
396
+ async function withListLock(action) {
397
+ await deps.fs.mkdir(listDirectoryPath, { recursive: true });
398
+ const release = await acquireFileLock(path.join(listDirectoryPath, ORDER_LOCK_FILENAME), {
298
399
  fs: deps.fs,
299
400
  staleMs: deps.lockStaleMs,
300
401
  retries: deps.lockRetries
@@ -306,6 +407,10 @@ function createTasksView(deps, list) {
306
407
  await release();
307
408
  }
308
409
  }
410
+ async function withTaskLock(id, action) {
411
+ validateTaskId(id);
412
+ return withListLock(action);
413
+ }
309
414
  async function getTaskFile(id) {
310
415
  validateTaskId(id);
311
416
  return readTaskAtLocation(deps.fs, deps.path, list, id, validStates);
@@ -326,29 +431,42 @@ function createTasksView(deps, list) {
326
431
  name: list,
327
432
  stateMachine,
328
433
  async all(filter) {
329
- const activeTasks = await listFiles(listDirectoryPath);
330
- const archivedTasks = filter?.includeArchived
331
- ? await listFiles(archiveDirectoryPath(deps.path, list))
332
- : [];
333
- const combinedTasks = [...activeTasks, ...archivedTasks];
334
- if (filter?.state) {
335
- return combinedTasks.filter((task) => task.state === filter.state);
336
- }
337
- return combinedTasks;
434
+ const { entries: activeEntries, tasks: activeTasks } = await readActiveTasks();
435
+ const archivedEntries = filter?.includeArchived ? await readArchivedTasks() : [];
436
+ const orderedActive = activeEntries
437
+ .map((entry) => activeTasks.get(entry.id))
438
+ .filter((entry) => {
439
+ if (filter?.state && entry.task.state !== filter.state)
440
+ return false;
441
+ return true;
442
+ });
443
+ const filteredArchived = archivedEntries.filter((entry) => {
444
+ if (filter?.state && entry.task.state !== filter.state)
445
+ return false;
446
+ return true;
447
+ });
448
+ const orderedActiveTasks = applyOrder(orderedActive, filter?.order);
449
+ return [...orderedActiveTasks, ...filteredArchived.map((entry) => entry.task)];
338
450
  },
339
451
  async get(id) {
340
452
  return (await getTaskFile(id)).task;
341
453
  },
342
454
  async create(input) {
343
455
  assertCreateDoesNotSetState(input);
456
+ assertCreateHasId(input);
344
457
  validateTaskId(input.id);
345
458
  await deps.fs.mkdir(listDirectoryPath, { recursive: true });
346
- return withTaskLock(input.id, async () => {
459
+ return withListLock(async () => {
347
460
  const existing = await findTaskLocation(deps.fs, deps.path, list, input.id);
348
461
  if (existing) {
349
462
  throw new TaskAlreadyExistsError(`Task "${list}/${input.id}" already exists.`);
350
463
  }
351
- const targetPath = activeTaskPath(deps.path, list, input.id);
464
+ const activeEntries = await readActiveEntries();
465
+ const maxOrder = activeEntries.reduce((max, entry) => (entry.order !== null && entry.order > max ? entry.order : max), 0);
466
+ const nextOrder = maxOrder + 1;
467
+ const width = padWidthForCount(activeEntries.length + 1);
468
+ const filename = activeTaskFilename(input.id, nextOrder, width);
469
+ const targetPath = path.join(listDirectoryPath, filename);
352
470
  const frontmatter = createdFrontmatter(deps.defaults, input, stateMachine.initial);
353
471
  const description = input.description ?? "";
354
472
  await writeAtomically(deps.fs, targetPath, serializeTaskDocument(frontmatter, description));
@@ -419,11 +537,58 @@ function createTasksView(deps, list) {
419
537
  }
420
538
  await deps.fs.unlink(location.path);
421
539
  });
540
+ },
541
+ async move(id, anchor) {
542
+ validateTaskId(id);
543
+ return withListLock(async () => {
544
+ const { entries, tasks } = await readActiveTasks();
545
+ const fromIndex = entries.findIndex((entry) => entry.id === id);
546
+ if (fromIndex < 0) {
547
+ throw new TaskNotFoundError(`Task "${list}/${id}" not found.`);
548
+ }
549
+ const ordered = entries.map((entry) => entry.id);
550
+ ordered.splice(fromIndex, 1);
551
+ let insertIndex;
552
+ if ("position" in anchor) {
553
+ insertIndex = anchor.position === "top" ? 0 : ordered.length;
554
+ }
555
+ else {
556
+ const anchorId = "before" in anchor ? anchor.before : anchor.after;
557
+ const anchorIndex = ordered.indexOf(anchorId);
558
+ if (anchorIndex < 0) {
559
+ throw new AnchorNotFoundError(anchorId);
560
+ }
561
+ insertIndex = "before" in anchor ? anchorIndex : anchorIndex + 1;
562
+ }
563
+ ordered.splice(insertIndex, 0, id);
564
+ await rewriteListPrefixes(ordered);
565
+ return tasks.get(id).task;
566
+ });
567
+ },
568
+ async reorder(ids) {
569
+ for (const id of ids) {
570
+ validateTaskId(id);
571
+ }
572
+ return withListLock(async () => {
573
+ const { entries, tasks } = await readActiveTasks();
574
+ const currentIds = entries.map((entry) => entry.id);
575
+ const currentSet = new Set(currentIds);
576
+ const inputSet = new Set(ids);
577
+ const missing = currentIds.filter((id) => !inputSet.has(id));
578
+ const extra = ids.filter((id) => !currentSet.has(id));
579
+ if (missing.length > 0 || extra.length > 0) {
580
+ throw new OrderMismatchError({ missing, extra });
581
+ }
582
+ await rewriteListPrefixes(ids);
583
+ return ids.map((id) => tasks.get(id).task);
584
+ });
422
585
  }
423
586
  };
424
587
  }
425
588
  export async function markdownDirBackend(deps) {
426
589
  await ensureRootPath(deps);
590
+ const stateMachine = resolveStateMachine(deps.stateMachine);
591
+ const validStates = new Set(stateMachine.states);
427
592
  const list = (name) => {
428
593
  const listName = validateListName(name);
429
594
  return createTasksView(deps, listName);
@@ -451,16 +616,63 @@ export async function markdownDirBackend(deps) {
451
616
  for (const taskListName of allLists) {
452
617
  tasks.push(...(await list(taskListName).all(filter)));
453
618
  }
454
- return sortTasks(tasks);
619
+ return tasks;
455
620
  };
456
621
  const get = async (qualifiedId) => {
457
622
  const { list: listName, id } = parseQualifiedId(qualifiedId);
458
623
  return list(listName).get(id);
459
624
  };
625
+ const moveBetweenLists = async (qualifiedId, targetList) => {
626
+ const { list: sourceListName, id } = parseQualifiedId(qualifiedId);
627
+ const targetListName = validateListName(targetList);
628
+ if (sourceListName === targetListName) {
629
+ const file = await readTaskAtLocation(deps.fs, deps.path, sourceListName, id, validStates);
630
+ return file.task;
631
+ }
632
+ const targetExisting = await findTaskLocation(deps.fs, deps.path, targetListName, id);
633
+ if (targetExisting) {
634
+ throw new TaskAlreadyExistsError(`Task "${targetListName}/${id}" already exists.`);
635
+ }
636
+ const sourceLocation = await findTaskLocation(deps.fs, deps.path, sourceListName, id);
637
+ if (!sourceLocation) {
638
+ throw new TaskNotFoundError(`Task "${sourceListName}/${id}" not found.`);
639
+ }
640
+ const targetListDir = listPath(deps.path, targetListName);
641
+ await deps.fs.mkdir(targetListDir, { recursive: true });
642
+ const targetEntries = await (async () => {
643
+ const out = [];
644
+ const names = await readDirectoryNames(deps.fs, targetListDir);
645
+ for (const entryName of names) {
646
+ if (isHiddenEntry(entryName) || isLockFile(entryName))
647
+ continue;
648
+ const parsed = parseActiveFilename(entryName);
649
+ if (!parsed)
650
+ continue;
651
+ out.push({ id: parsed.id, order: parsed.order, filename: entryName });
652
+ }
653
+ return out;
654
+ })();
655
+ if (sourceLocation.archived) {
656
+ const archivedTargetDir = archiveDirectoryPath(deps.path, targetListName);
657
+ await deps.fs.mkdir(archivedTargetDir, { recursive: true });
658
+ const archivedTargetPath = archivedTaskPath(deps.path, targetListName, id);
659
+ await deps.fs.rename(sourceLocation.path, archivedTargetPath);
660
+ const file = await readTaskFile(deps.fs, targetListName, id, archivedTargetPath, validStates);
661
+ return file.task;
662
+ }
663
+ const maxOrder = targetEntries.reduce((max, entry) => (entry.order !== null && entry.order > max ? entry.order : max), 0);
664
+ const width = padWidthForCount(targetEntries.length + 1);
665
+ const targetFilename = activeTaskFilename(id, maxOrder + 1, width);
666
+ const targetPath = path.join(targetListDir, targetFilename);
667
+ await deps.fs.rename(sourceLocation.path, targetPath);
668
+ const file = await readTaskFile(deps.fs, targetListName, id, targetPath, validStates);
669
+ return file.task;
670
+ };
460
671
  return {
461
672
  list,
462
673
  lists,
463
674
  allTasks,
464
- get
675
+ get,
676
+ moveBetweenLists
465
677
  };
466
678
  }
@@ -1,4 +1,10 @@
1
- import type { Task, TaskListFs } from "../types.js";
1
+ import type { ListFilter, Task, TaskListFs } from "../types.js";
2
+ export interface OrderedEntry {
3
+ task: Task;
4
+ raw: Record<string, unknown>;
5
+ }
6
+ export declare function compareCreated(left: OrderedEntry, right: OrderedEntry): number;
7
+ export declare function applyOrder(entries: OrderedEntry[], order: ListFilter["order"]): Task[];
2
8
  export declare function hasErrorCode(error: unknown, code: string): boolean;
3
9
  export declare function isRecord(value: unknown): value is Record<string, unknown>;
4
10
  export declare function sortStrings(values: string[]): string[];
@@ -1,4 +1,25 @@
1
1
  import path from "node:path";
2
+ export function compareCreated(left, right) {
3
+ const leftCreated = typeof left.raw.created === "string" ? left.raw.created : "";
4
+ const rightCreated = typeof right.raw.created === "string" ? right.raw.created : "";
5
+ if (leftCreated === "" && rightCreated === "") {
6
+ return left.task.qualifiedId.localeCompare(right.task.qualifiedId);
7
+ }
8
+ if (leftCreated === "")
9
+ return 1;
10
+ if (rightCreated === "")
11
+ return -1;
12
+ return leftCreated.localeCompare(rightCreated);
13
+ }
14
+ export function applyOrder(entries, order) {
15
+ if (order === "alphabetical") {
16
+ return sortTasks(entries.map((entry) => entry.task));
17
+ }
18
+ if (order === "created") {
19
+ return [...entries].sort(compareCreated).map((entry) => entry.task);
20
+ }
21
+ return entries.map((entry) => entry.task);
22
+ }
2
23
  let tmpFileCounter = 0;
3
24
  export function hasErrorCode(error, code) {
4
25
  return (!!error &&