@zinn-dev/cli 0.7.0 → 0.9.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
@@ -84,6 +84,9 @@ Tasks from separate projects can be related to each other. Tasks can also be rel
84
84
 
85
85
  `task view` a task's existing relations.
86
86
 
87
+ Remove a relation with `zinn task relation delete MDR-1 MDR-2 --type dependency`, using its original direction for `dependency` and `duplicate`. For `related`, order does not matter.
88
+ Note that a deletion happens _without_ confirmation.
89
+
87
90
  Use `task relation create --help` to discover types.
88
91
 
89
92
  ## Edit tasks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zinn-dev/cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.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.7.0"
45
+ "@zinn-dev/core": "0.9.0"
46
46
  }
47
47
  }
@@ -1,7 +1,7 @@
1
1
  import type { RouteDef } from "../types";
2
2
 
3
3
  import { parseArgs } from "node:util";
4
- import { project, relation, relationCreateSchema, task } from "@zinn-dev/core";
4
+ import { relation, relationCreateSchema, task } from "@zinn-dev/core";
5
5
  import { quit } from "../lib";
6
6
 
7
7
  export function formatRelations(taskKey: string) {
@@ -38,46 +38,79 @@ export function formatRelations(taskKey: string) {
38
38
  .join("\n");
39
39
  }
40
40
 
41
+ function parseRelationArgs(args: string[]) {
42
+ const { values, positionals, tokens } = parseArgs({
43
+ args,
44
+ options: { type: { type: "string" } },
45
+ allowPositionals: true,
46
+ strict: true,
47
+ tokens: true,
48
+ });
49
+
50
+ if (positionals.length !== 2) {
51
+ quit("Exactly two task keys are required: source, target");
52
+ }
53
+
54
+ const optionCount = tokens.filter((token) => token.kind === "option").length;
55
+
56
+ if (optionCount > 1) {
57
+ // TODO: maybe a reusable option validator can be given expected option keys, and it can spit out unsupported options as well as help for what options are supported
58
+ quit('Option "--type" can only be specified once');
59
+ }
60
+
61
+ const typeResult = relationCreateSchema.shape.relation_type.safeParse(values.type);
62
+
63
+ if (!typeResult.success) {
64
+ quit("--type must be one of these: related, dependency, duplicate");
65
+ }
66
+ const sourceTaskKey = positionals[0]!;
67
+ const targetTaskKey = positionals[1]!;
68
+
69
+ return { sourceTaskKey, targetTaskKey, relation_type: typeResult.data };
70
+ }
71
+
41
72
  export const relationRouter = {
42
- create: {
73
+ delete: {
43
74
  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
- }
75
+ const props = parseRelationArgs(args);
76
+ relation.delete(props);
55
77
 
56
- if (tokens.filter((token) => token.kind === "option").length > 1) {
57
- quit('Option "--type" can only be specified once');
58
- }
78
+ const source = task.getByKey(props.sourceTaskKey);
79
+ const target = task.getByKey(props.targetTaskKey);
80
+ console.info(`Deleted ${props.relation_type} relation from ${source.key} to ${target.key}`);
81
+ },
82
+ help: `Usage: zinn task relation delete <source-task-key> <target-task-key> --type <type>
59
83
 
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]!;
84
+ Remove only the specified relation. No confirmation is required.
85
+
86
+ Types: related, dependency, duplicate.
66
87
 
67
- relation.create({ sourceTaskKey, targetTaskKey, relation_type: typeResult.data });
88
+ For related, either task order works.
89
+ For dependency and duplicate, use the creation direction:
90
+ source depends on target, or source is a duplicate of target.
91
+
92
+ Archived and cross-project tasks are supported.
93
+ A missing relation is an error.
94
+
95
+ Example: zinn task relation delete APP-1 APP-2 --type dependency`,
96
+ },
97
+ create: {
98
+ run: (args) => {
99
+ const props = parseRelationArgs(args);
100
+ const { sourceTaskKey, targetTaskKey } = props;
101
+
102
+ relation.create(props);
68
103
 
69
104
  const source = task.getByKey(sourceTaskKey);
70
105
  const target = task.getByKey(targetTaskKey);
71
106
 
72
- const sourceKey = `${project.getById(source.project_id)!.key}-${source.number}`;
73
- const targetKey = `${project.getById(target.project_id)!.key}-${target.number}`;
74
-
75
107
  const label = {
76
108
  related: "related to",
77
109
  dependency: "depends on",
78
110
  duplicate: "is a duplicate of",
79
- }[typeResult.data];
80
- console.info(`${sourceKey} ${label} ${targetKey}`);
111
+ }[props.relation_type];
112
+
113
+ console.info(`${source.key} ${label} ${target.key}`);
81
114
  },
82
115
  help: `Usage: zinn task relation create <source-task-key> <target-task-key> --type <type>
83
116
 
@@ -10,9 +10,8 @@ function formatTask(
10
10
  taskMatch: ReturnType<typeof task.getByKey>,
11
11
  options: { showsStatus?: boolean } = {},
12
12
  ) {
13
- const taskProject = project.getById(taskMatch.project_id)!;
14
13
  const taskColumn = column.getById(taskMatch.column_id)!;
15
- const taskKey = `${taskProject.key}-${taskMatch.number}`;
14
+ const taskKey = taskMatch.key;
16
15
  const status = options.showsStatus
17
16
  ? ` | ${taskMatch.archived_at == null ? "Active" : "Archived"}`
18
17
  : "";
@@ -140,10 +139,8 @@ Example: zinn task create MDR "Refine the numbers" "Sort the numbers and meet th
140
139
  }
141
140
 
142
141
  const presentableTasks = tasks.map((t) => {
143
- // TODO: A task's project and column is fetched in both `list` and `view`. Make a reusable fetcher?
144
- const taskProject = project.getById(t.project_id)!;
145
142
  const taskColumn = column.getById(t.column_id)!;
146
- const taskKey = `${taskProject.key}-${t.number}`;
143
+ const taskKey = t.key;
147
144
 
148
145
  return {
149
146
  id: taskKey,