@harborppm/cli 0.11.2 → 0.13.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 +9 -5
- package/dist/app.mjs +1159 -282
- package/lib/cli.mjs +60 -7
- package/lib/format.mjs +25 -0
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import {
|
|
20
20
|
harbors,
|
|
21
21
|
myWork,
|
|
22
|
+
raidList,
|
|
22
23
|
programTree,
|
|
23
24
|
projectList,
|
|
24
25
|
taskLine,
|
|
@@ -59,13 +60,18 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
59
60
|
[--over-limit]
|
|
60
61
|
harbor start TASK | review TASK | done TASK [--confirm] [--over-limit]
|
|
61
62
|
harbor edit TASK [--title T] [--assignee me|none|NAME] [--due DATE|none]
|
|
62
|
-
[--priority P] [--status S] [--
|
|
63
|
+
[--priority P] [--status S] [--notes TEXT]
|
|
64
|
+
[--repeat weekly|biweekly|monthly|never]
|
|
65
|
+
[--waits-on TASK,TASK|none] [--confirm] [--over-limit]
|
|
63
66
|
harbor comment TASK "TEXT"
|
|
64
67
|
harbor checkin "WHAT YOU'RE DOING" [--on-blocker]
|
|
65
68
|
harbor risk "TITLE" --project P|--program P [--impact 1-5] [--probability 1-5]
|
|
66
69
|
[--owner NAME] [--due DATE]
|
|
67
70
|
harbor issue "TITLE" --project P|--program P [--impact 1-5] [--owner NAME]
|
|
68
71
|
[--due DATE]
|
|
72
|
+
harbor decision "TITLE" --project P|--program P [--owner NAME] [--due DATE]
|
|
73
|
+
harbor risks [words] [--kind risk,issue,decision] [--project P] [--closed]
|
|
74
|
+
the register, worst first
|
|
69
75
|
|
|
70
76
|
Every command takes --json, and --harbor NAME when you're in more than one
|
|
71
77
|
harbor (or set one with harbor use). harbor help COMMAND shows just that one.
|
|
@@ -144,6 +150,7 @@ const level = (v, name) => {
|
|
|
144
150
|
return n;
|
|
145
151
|
};
|
|
146
152
|
const nullable = (v) => (v === "none" ? null : v);
|
|
153
|
+
const REPEAT_WORD = { weekly: "every week", biweekly: "every two weeks", monthly: "every month" };
|
|
147
154
|
|
|
148
155
|
/** --due as people say it: today, tomorrow, +3, or a date. Anything else goes to Harbor to judge. */
|
|
149
156
|
export function dueDate(v, today = new Date()) {
|
|
@@ -176,6 +183,9 @@ const FLAG = {
|
|
|
176
183
|
query: "the words",
|
|
177
184
|
task: "the task",
|
|
178
185
|
note: "the check-in",
|
|
186
|
+
waitsOn: "--waits-on",
|
|
187
|
+
repeat: "--repeat",
|
|
188
|
+
description: "--notes",
|
|
179
189
|
body: "the comment",
|
|
180
190
|
};
|
|
181
191
|
|
|
@@ -213,7 +223,7 @@ export function hint(message) {
|
|
|
213
223
|
.replace(/Find it with search_tasks\.$/, "Find it with harbor tasks.")
|
|
214
224
|
.replace(
|
|
215
225
|
/^Nothing to change: pass a field to update\.$/,
|
|
216
|
-
"Nothing to change. Say what with --title, --assignee, --due, --priority or --
|
|
226
|
+
"Nothing to change. Say what with --title, --assignee, --due, --priority, --status, --notes, --repeat or --waits-on.",
|
|
217
227
|
)
|
|
218
228
|
.replace(
|
|
219
229
|
/^This connection was allowed to read only\..*$/s,
|
|
@@ -782,8 +792,12 @@ export async function run(argv, io = {}) {
|
|
|
782
792
|
}
|
|
783
793
|
case "edit": {
|
|
784
794
|
const { positionals, values, harbor } = parse(
|
|
785
|
-
spec(
|
|
795
|
+
spec(
|
|
796
|
+
["confirm", "over-limit"],
|
|
797
|
+
["title", "assignee", "due", "priority", "status", "notes", "repeat", "waits-on"],
|
|
798
|
+
),
|
|
786
799
|
);
|
|
800
|
+
const waits = values["waits-on"];
|
|
787
801
|
return call(
|
|
788
802
|
"update_task",
|
|
789
803
|
{
|
|
@@ -794,10 +808,28 @@ export async function run(argv, io = {}) {
|
|
|
794
808
|
due: values.due === undefined ? undefined : nullable(dueDate(values.due)),
|
|
795
809
|
priority: values.priority,
|
|
796
810
|
status: values.status,
|
|
811
|
+
description: values.notes,
|
|
812
|
+
repeat: values.repeat,
|
|
813
|
+
waitsOn:
|
|
814
|
+
waits === undefined
|
|
815
|
+
? undefined
|
|
816
|
+
: waits === "none"
|
|
817
|
+
? []
|
|
818
|
+
: waits
|
|
819
|
+
.split(",")
|
|
820
|
+
.map((x) => x.trim())
|
|
821
|
+
.filter(Boolean),
|
|
797
822
|
confirmed: values.confirm || undefined,
|
|
798
823
|
overLimit: values["over-limit"] || undefined,
|
|
799
824
|
},
|
|
800
|
-
(r) =>
|
|
825
|
+
(r) =>
|
|
826
|
+
[
|
|
827
|
+
`Updated: ${taskLine(r.updated, opts)}`,
|
|
828
|
+
r.updated.waitsOn ? `Waits on ${r.updated.waitsOn.join(", ")}.` : null,
|
|
829
|
+
r.updated.repeat ? `Repeats ${REPEAT_WORD[r.updated.repeat]}.` : null,
|
|
830
|
+
]
|
|
831
|
+
.filter(Boolean)
|
|
832
|
+
.join("\n"),
|
|
801
833
|
);
|
|
802
834
|
}
|
|
803
835
|
case "comment": {
|
|
@@ -823,13 +855,34 @@ export async function run(argv, io = {}) {
|
|
|
823
855
|
() => "Checked in for today.",
|
|
824
856
|
);
|
|
825
857
|
}
|
|
858
|
+
case "risks": {
|
|
859
|
+
const { positionals, values, harbor } = parse(spec(["closed"], ["kind", "project"]));
|
|
860
|
+
const kind = values.kind
|
|
861
|
+
?.split(",")
|
|
862
|
+
.map((x) => x.trim().replace(/s$/, ""))
|
|
863
|
+
.filter(Boolean);
|
|
864
|
+
const bad = kind?.find((k) => !["risk", "issue", "decision"].includes(k));
|
|
865
|
+
if (bad) throw new HarborError(`--kind is risk, issue or decision (not "${bad}").`);
|
|
866
|
+
return call(
|
|
867
|
+
"list_risks",
|
|
868
|
+
{
|
|
869
|
+
harbor,
|
|
870
|
+
kind,
|
|
871
|
+
project: values.project,
|
|
872
|
+
includeClosed: Boolean(values.closed),
|
|
873
|
+
limit: 50,
|
|
874
|
+
},
|
|
875
|
+
(r) => raidList(r, positionals.join(" ")),
|
|
876
|
+
);
|
|
877
|
+
}
|
|
826
878
|
case "risk":
|
|
827
|
-
case "issue":
|
|
879
|
+
case "issue":
|
|
880
|
+
case "decision": {
|
|
828
881
|
const { positionals, values, harbor } = parse(
|
|
829
882
|
spec(
|
|
830
883
|
[],
|
|
831
|
-
["project", "program", "owner", "
|
|
832
|
-
command === "risk" ? ["probability"] : [],
|
|
884
|
+
["project", "program", "owner", "due"].concat(
|
|
885
|
+
command === "risk" ? ["impact", "probability"] : command === "issue" ? ["impact"] : [],
|
|
833
886
|
),
|
|
834
887
|
),
|
|
835
888
|
);
|
package/lib/format.mjs
CHANGED
|
@@ -158,3 +158,28 @@ export function todayLocal(d = new Date()) {
|
|
|
158
158
|
const p = (n) => String(n).padStart(2, "0");
|
|
159
159
|
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
/** The register, worst first: kind, title, then status, scores, owner and where it sits. */
|
|
163
|
+
export function raidList(r, words = "") {
|
|
164
|
+
const want = words.trim().toLowerCase();
|
|
165
|
+
const items = (r.items ?? []).filter(
|
|
166
|
+
(x) => !want || `${x.title} ${x.description ?? ""}`.toLowerCase().includes(want),
|
|
167
|
+
);
|
|
168
|
+
if (!items.length) return want ? "Nothing in the register matches." : "The register is clear.";
|
|
169
|
+
const lines = items.map((x) => {
|
|
170
|
+
const bits = [
|
|
171
|
+
x.status?.toLowerCase(),
|
|
172
|
+
x.probability != null ? `likelihood ${x.probability}` : null,
|
|
173
|
+
x.impact != null ? `impact ${x.impact}` : null,
|
|
174
|
+
x.owner,
|
|
175
|
+
x.project ?? x.program ?? x.portfolio ?? null,
|
|
176
|
+
x.due ? `${x.kind === "Decision" ? "decide by" : "review by"} ${x.due}` : null,
|
|
177
|
+
].filter(Boolean);
|
|
178
|
+
return `${x.kind.toLowerCase().padEnd(8)} ${x.title} (${bits.join(" · ")})`;
|
|
179
|
+
});
|
|
180
|
+
const more =
|
|
181
|
+
!want && r.total > items.length
|
|
182
|
+
? `\n…and ${r.total - items.length} more. Narrow it with --kind or --project.`
|
|
183
|
+
: "";
|
|
184
|
+
return lines.join("\n") + more;
|
|
185
|
+
}
|