@harborppm/cli 0.30.0 → 0.31.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/lib/cli.mjs +9 -3
- package/lib/format.mjs +56 -8
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
STATUS,
|
|
23
23
|
harbors,
|
|
24
24
|
limitsText,
|
|
25
|
+
hillData,
|
|
25
26
|
hillList,
|
|
26
27
|
myWork,
|
|
27
28
|
premortemText,
|
|
@@ -899,7 +900,12 @@ export async function run(argv, io = {}) {
|
|
|
899
900
|
if (!project) {
|
|
900
901
|
if (values.at !== undefined)
|
|
901
902
|
throw new HarborError("Say which project: harbor hill PROJECT --at N.");
|
|
902
|
-
return call(
|
|
903
|
+
return call(
|
|
904
|
+
"list_projects",
|
|
905
|
+
{ harbor, program: values.program, limit: 100 },
|
|
906
|
+
hillList,
|
|
907
|
+
(r) => hillData(r, values.program),
|
|
908
|
+
);
|
|
903
909
|
}
|
|
904
910
|
if (values.at === undefined) {
|
|
905
911
|
throw new HarborError("Say where with --at: 0 to 100, or start, top, doing or done.");
|
|
@@ -1299,7 +1305,7 @@ export async function run(argv, io = {}) {
|
|
|
1299
1305
|
if (!project) throw new HarborError("Give a project, like harbor limit RR doing 5.");
|
|
1300
1306
|
if (!column) {
|
|
1301
1307
|
return call("get_project", { harbor, project }, (r) =>
|
|
1302
|
-
limitsText(r.name, r.limits, r.tasks),
|
|
1308
|
+
limitsText(r.name, r.limits, r.tasks, r.hiddenColumns),
|
|
1303
1309
|
);
|
|
1304
1310
|
}
|
|
1305
1311
|
if (value === undefined || extra.length) {
|
|
@@ -1310,7 +1316,7 @@ export async function run(argv, io = {}) {
|
|
|
1310
1316
|
throw new HarborError("A limit is a whole number from 1 to 20, or none.");
|
|
1311
1317
|
}
|
|
1312
1318
|
return call("set_column_limit", { harbor, project, column, limit }, (r) =>
|
|
1313
|
-
limitsText(r.set.project, r.limits, r.tasks),
|
|
1319
|
+
limitsText(r.set.project, r.limits, r.tasks, r.hiddenColumns),
|
|
1314
1320
|
);
|
|
1315
1321
|
}
|
|
1316
1322
|
case "voyage": {
|
package/lib/format.mjs
CHANGED
|
@@ -144,16 +144,26 @@ export function projectList(r, { done = false } = {}) {
|
|
|
144
144
|
);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/**
|
|
148
|
-
|
|
147
|
+
/**
|
|
148
|
+
* A project's column limits with how full each is: "doing 4 of 5; no limit on
|
|
149
|
+
* backlog, todo, done". Columns the board hides are named after, with any cap
|
|
150
|
+
* they keep for when they're shown again.
|
|
151
|
+
*/
|
|
152
|
+
export function limitsText(name, limits, tasks = {}, hidden = []) {
|
|
153
|
+
const label = (status) => STATUS[status] ?? status;
|
|
149
154
|
const entries = Object.entries(limits ?? {});
|
|
150
|
-
const
|
|
155
|
+
const shown = entries.filter(([status]) => !hidden.includes(status));
|
|
156
|
+
const capped = shown
|
|
151
157
|
.filter(([, n]) => n != null)
|
|
152
|
-
.map(([status, n]) => `${
|
|
153
|
-
const open =
|
|
154
|
-
|
|
158
|
+
.map(([status, n]) => `${label(status)} ${tasks[status] ?? 0} of ${n}`);
|
|
159
|
+
const open = shown.filter(([, n]) => n == null).map(([status]) => label(status));
|
|
160
|
+
const off = entries
|
|
161
|
+
.filter(([status]) => hidden.includes(status))
|
|
162
|
+
.map(([status, n]) => (n == null ? label(status) : `${label(status)} (limit ${n})`));
|
|
163
|
+
const offNote = off.length ? ` Hidden on the board: ${off.join(", ")}.` : "";
|
|
164
|
+
if (!capped.length) return `${name}'s board has no column limits.${offNote}`;
|
|
155
165
|
const rest = open.length ? `; no limit on ${open.join(", ")}` : "";
|
|
156
|
-
return `${name}: ${capped.join(", ")}${rest}
|
|
166
|
+
return `${name}: ${capped.join(", ")}${rest}.${offNote}`;
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
export function harbors(r) {
|
|
@@ -295,10 +305,43 @@ function hillTrack(at) {
|
|
|
295
305
|
return cells.join("");
|
|
296
306
|
}
|
|
297
307
|
|
|
308
|
+
/**
|
|
309
|
+
* What harbor hill shows, for the chart and --json alike: open projects with
|
|
310
|
+
* their place on the hill (null when not on it), how many Done ones were left
|
|
311
|
+
* out, and the program asked for.
|
|
312
|
+
*/
|
|
313
|
+
export function hillData(r, program) {
|
|
314
|
+
const all = r.projects ?? [];
|
|
315
|
+
const open = all.filter((p) => p.health !== "done");
|
|
316
|
+
// The program's own name when every project is in it, else what was typed.
|
|
317
|
+
const named =
|
|
318
|
+
all.length && all.every((p) => p.program === all[0].program) ? all[0].program : null;
|
|
319
|
+
return {
|
|
320
|
+
...(program ? { program: named ?? program } : {}),
|
|
321
|
+
projects: open.map((p) => ({
|
|
322
|
+
id: p.id,
|
|
323
|
+
name: p.name,
|
|
324
|
+
...(p.key ? { key: p.key } : {}),
|
|
325
|
+
program: p.program ?? null,
|
|
326
|
+
health: p.health,
|
|
327
|
+
hill: p.hill ?? null,
|
|
328
|
+
})),
|
|
329
|
+
done: all.length - open.length,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
298
333
|
/** Each open project's place on its program's hill, by program, unplaced ones named after. */
|
|
299
334
|
export function hillList(r) {
|
|
300
335
|
const projects = (r.projects ?? []).filter((p) => p.health !== "done");
|
|
301
|
-
if (!projects.length)
|
|
336
|
+
if (!projects.length) {
|
|
337
|
+
if (r.done) {
|
|
338
|
+
const where = r.program ? `in ${r.program} ` : "";
|
|
339
|
+
return r.done === 1
|
|
340
|
+
? `The one project ${where}is Done, so nothing is on the hill.`
|
|
341
|
+
: `All ${r.done} projects ${where}are Done, so nothing is on the hill.`;
|
|
342
|
+
}
|
|
343
|
+
return "No open projects match.";
|
|
344
|
+
}
|
|
302
345
|
const byProgram = new Map();
|
|
303
346
|
for (const p of projects) {
|
|
304
347
|
const key = p.program ?? "No program";
|
|
@@ -317,6 +360,11 @@ export function hillList(r) {
|
|
|
317
360
|
lines.push(` ${name} ${hillTrack(p.hill.at)} ${String(p.hill.at).padStart(3)}${still}`);
|
|
318
361
|
}
|
|
319
362
|
if (unplaced.length) lines.push(` Not on the hill: ${unplaced.map((p) => p.name).join(", ")}`);
|
|
363
|
+
if (!placed.length) {
|
|
364
|
+
lines.push(
|
|
365
|
+
` Place one with harbor hill "${unplaced[0].name}" --at 30 (0 to 100, top is 50).`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
320
368
|
blocks.push(lines.join("\n"));
|
|
321
369
|
}
|
|
322
370
|
return blocks.join("\n\n");
|