@zinn-dev/cli 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,6 +68,24 @@ zinn task view MDR-1
68
68
 
69
69
  When a project key is supplied, the list follows the project's column order and the task order within each column.
70
70
 
71
+ ## Task relations
72
+
73
+ ```sh
74
+ zinn task relation create MDR-1 MDR-2 --type dependency
75
+ zinn task relation create MDR-1 MDR-2 --type duplicate
76
+ zinn task relation create MDR-1 OTHER-1 --type related
77
+ zinn task relation list MDR-1
78
+ ```
79
+
80
+ `dependency` means the first task depends on the second, `duplicate` means the first task is a duplicate of the second. `related` is a bit different as the order of the tasks mentioned _does not matter_, either case describes the same relation.
81
+
82
+ Relations do not have any side effects. They are purely for information.
83
+ Tasks from separate projects can be related to each other. Tasks can also be related to archived tasks.
84
+
85
+ `task view` a task's existing relations.
86
+
87
+ Use `task relation create --help` to discover types.
88
+
71
89
  ## Edit tasks
72
90
 
73
91
  Edit a task's title, description, or both:
package/index.ts CHANGED
@@ -25,6 +25,8 @@ Commands:
25
25
  task archive
26
26
  task unarchive
27
27
  task delete
28
+ task relation create
29
+ task relation list
28
30
 
29
31
  Run zinn <namespace> --help or zinn <command> --help for more information.
30
32
  Running zinn without a command shows this help.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zinn-dev/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "A kanban workflow in the terminal.",
5
5
  "license": "MIT",
6
6
  "bugs": {
@@ -42,6 +42,6 @@
42
42
  "access": "public"
43
43
  },
44
44
  "dependencies": {
45
- "@zinn-dev/core": "0.5.0"
45
+ "@zinn-dev/core": "0.7.0"
46
46
  }
47
47
  }
@@ -0,0 +1,113 @@
1
+ import type { RouteDef } from "../types";
2
+
3
+ import { parseArgs } from "node:util";
4
+ import { project, relation, relationCreateSchema, task } from "@zinn-dev/core";
5
+ import { quit } from "../lib";
6
+
7
+ export function formatRelations(taskKey: string) {
8
+ const taskMatch = task.getByKey(taskKey);
9
+
10
+ return relation
11
+ .getAll(taskKey, { include: { tasks: true } })
12
+ .map((entry) => {
13
+ const isSource = entry.source_task_id === taskMatch.id;
14
+ const other = isSource ? entry.target : entry.source;
15
+ if (other == null) {
16
+ throw new Error("Connected task details were not included in the relation");
17
+ }
18
+
19
+ let label: string;
20
+ switch (entry.relation_type) {
21
+ case "related": {
22
+ label = "Related to";
23
+ break;
24
+ }
25
+ case "dependency": {
26
+ label = isSource ? "Depends on" : "Depended on by";
27
+ break;
28
+ }
29
+ case "duplicate": {
30
+ label = isSource ? "Duplicate of" : "Duplicated by";
31
+ break;
32
+ }
33
+ }
34
+
35
+ const archived = other.archived_at == null ? "" : " | Archived";
36
+ return `${label} | ${other.key} | ${other.title}${archived}`;
37
+ })
38
+ .join("\n");
39
+ }
40
+
41
+ export const relationRouter = {
42
+ create: {
43
+ run: (args) => {
44
+ const { values, positionals, tokens } = parseArgs({
45
+ args,
46
+ options: { type: { type: "string" } },
47
+ allowPositionals: true,
48
+ strict: true,
49
+ tokens: true,
50
+ });
51
+
52
+ if (positionals.length !== 2) {
53
+ quit("Provide exactly two task keys: source and target");
54
+ }
55
+
56
+ if (tokens.filter((token) => token.kind === "option").length > 1) {
57
+ quit('Option "--type" can only be specified once');
58
+ }
59
+
60
+ const typeResult = relationCreateSchema.shape.relation_type.safeParse(values.type);
61
+ if (!typeResult.success) {
62
+ quit("Provide --type with one of: related, dependency, duplicate");
63
+ }
64
+ const sourceTaskKey = positionals[0]!;
65
+ const targetTaskKey = positionals[1]!;
66
+
67
+ relation.create({ sourceTaskKey, targetTaskKey, relation_type: typeResult.data });
68
+
69
+ const source = task.getByKey(sourceTaskKey);
70
+ const target = task.getByKey(targetTaskKey);
71
+
72
+ const sourceKey = `${project.getById(source.project_id)!.key}-${source.number}`;
73
+ const targetKey = `${project.getById(target.project_id)!.key}-${target.number}`;
74
+
75
+ const label = {
76
+ related: "related to",
77
+ dependency: "depends on",
78
+ duplicate: "is a duplicate of",
79
+ }[typeResult.data];
80
+ console.info(`${sourceKey} ${label} ${targetKey}`);
81
+ },
82
+ help: `Usage: zinn task relation create <source-task-key> <target-task-key> --type <type>
83
+
84
+ Types:
85
+ related Connect two tasks, with any order provided to indicate relation.
86
+ dependency Source depends on target.
87
+ duplicate Source is a duplicate of target.
88
+
89
+ Relations do not have any side effects. They are purely for information.
90
+ Tasks from separate projects can be related to each other. Tasks can also be related to archived tasks.
91
+
92
+ Example: zinn task relation create APP-1 APP-2 --type dependency`,
93
+ },
94
+ list: {
95
+ run: (args) => {
96
+ const { positionals } = parseArgs({ args, allowPositionals: true, strict: true });
97
+ if (positionals.length !== 1) {
98
+ quit("Provide exactly one task key");
99
+ }
100
+ const output = formatRelations(positionals[0]!);
101
+ if (output.length > 0) {
102
+ console.info(output);
103
+ }
104
+ },
105
+ help: `Usage: zinn task relation list <task-key>
106
+
107
+ Show incoming and outgoing relations with connected task keys and titles.
108
+ Archived connected tasks are included and marked Archived.
109
+ An empty list prints nothing.
110
+
111
+ Example: zinn task relation list APP-1`,
112
+ },
113
+ } satisfies RouteDef;
@@ -1,9 +1,10 @@
1
1
  import type { RouteDef } from "../types";
2
2
 
3
3
  import { parseArgs } from "node:util";
4
-
5
4
  import { task, project, column } from "@zinn-dev/core";
5
+
6
6
  import { quit } from "../lib";
7
+ import { formatRelations, relationRouter } from "./relation-router";
7
8
 
8
9
  function formatTask(
9
10
  taskMatch: ReturnType<typeof task.getByKey>,
@@ -21,6 +22,7 @@ function formatTask(
21
22
  }
22
23
 
23
24
  export const taskRouter = {
25
+ relation: relationRouter,
24
26
  edit: {
25
27
  run: (args) => {
26
28
  const { values, positionals, tokens } = parseArgs({
@@ -189,10 +191,15 @@ Example: zinn task list MDR --all`,
189
191
 
190
192
  const taskMatch = task.getByKey(taskKey);
191
193
  console.info(formatTask(taskMatch));
194
+
195
+ const relations = formatRelations(taskKey);
196
+ if (relations.length > 0) {
197
+ console.info(`\nRelations:\n${relations}`);
198
+ }
192
199
  },
193
200
  help: `Usage: zinn task view <task-key>
194
201
 
195
- Show a task with its current column and description.
202
+ Show a task with its current column, description, and relations.
196
203
 
197
204
  Example: zinn task view MDR-1`,
198
205
  },