@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 +18 -0
- package/index.ts +2 -0
- package/package.json +2 -2
- package/src/routes/relation-router.ts +113 -0
- package/src/routes/task-router.ts +9 -2
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zinn-dev/cli",
|
|
3
|
-
"version": "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.
|
|
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
|
|
202
|
+
Show a task with its current column, description, and relations.
|
|
196
203
|
|
|
197
204
|
Example: zinn task view MDR-1`,
|
|
198
205
|
},
|