@harborppm/cli 0.88.0 → 0.90.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/bin/harbor.mjs +3 -2
- package/dist/app.mjs +34 -7
- package/lib/exit.mjs +18 -0
- package/package.json +1 -1
package/bin/harbor.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { VERSION, run } from "../lib/cli.mjs";
|
|
3
|
+
import { exitWhenWritten } from "../lib/exit.mjs";
|
|
3
4
|
import { HarborError } from "../lib/mcp.mjs";
|
|
4
5
|
import { jsonText, plain } from "../lib/plain.mjs";
|
|
5
6
|
import { updateNotice } from "../lib/update.mjs";
|
|
@@ -19,7 +20,7 @@ const tell = async () => {
|
|
|
19
20
|
run(argv).then(
|
|
20
21
|
async (code) => {
|
|
21
22
|
await tell();
|
|
22
|
-
|
|
23
|
+
await exitWhenWritten(code);
|
|
23
24
|
},
|
|
24
25
|
async (err) => {
|
|
25
26
|
const known =
|
|
@@ -33,6 +34,6 @@ run(argv).then(
|
|
|
33
34
|
if (argv.includes("--json")) process.stdout.write(`${jsonText({ error: message })}\n`);
|
|
34
35
|
else process.stderr.write(`${plain(message)}\n`);
|
|
35
36
|
await tell();
|
|
36
|
-
|
|
37
|
+
await exitWhenWritten(1);
|
|
37
38
|
},
|
|
38
39
|
);
|
package/dist/app.mjs
CHANGED
|
@@ -4944,7 +4944,7 @@ function normalizeBoardConfig(raw) {
|
|
|
4944
4944
|
const todo = columns.find((c) => c.status === "todo") ?? columns[0];
|
|
4945
4945
|
todo.visible = true;
|
|
4946
4946
|
}
|
|
4947
|
-
const swimlane = raw.swimlane === "assignee" || raw.swimlane === "priority" ? raw.swimlane : "none";
|
|
4947
|
+
const swimlane = raw.swimlane === "assignee" || raw.swimlane === "priority" ? raw.swimlane : typeof raw.swimlane === "string" && /^field:[^\s]{1,100}$/.test(raw.swimlane) ? raw.swimlane : "none";
|
|
4948
4948
|
const cardFields = {
|
|
4949
4949
|
dueDate: raw.cardFields?.dueDate !== false,
|
|
4950
4950
|
assignee: raw.cardFields?.assignee !== false,
|
|
@@ -19723,6 +19723,18 @@ function undatedText(pole) {
|
|
|
19723
19723
|
return n === 1 ? "1 open task has no due date, so it can't hold the date." : `${n} open tasks have no due date, so they can't hold the date.`;
|
|
19724
19724
|
}
|
|
19725
19725
|
|
|
19726
|
+
// src/lib/raid-register.ts
|
|
19727
|
+
init_raid();
|
|
19728
|
+
/**
|
|
19729
|
+
* What agents and `harbor risks` list unless asked for everything: anything
|
|
19730
|
+
* still needing attention, plus decisions that stand. A decision is decided
|
|
19731
|
+
* once and then holds, so leaving it out the moment it's settled read as the
|
|
19732
|
+
* decision vanishing. Only one replaced by a newer decision drops out.
|
|
19733
|
+
*/
|
|
19734
|
+
function onLiveRegister(item) {
|
|
19735
|
+
return isActive(item) || item.kind === "decision" && item.status === "decided";
|
|
19736
|
+
}
|
|
19737
|
+
|
|
19726
19738
|
// src/lib/csv.ts
|
|
19727
19739
|
/**
|
|
19728
19740
|
* Text a spreadsheet would run as a formula: a title from an intake form
|
|
@@ -21756,7 +21768,7 @@ const HARBOR_TOOLS = [
|
|
|
21756
21768
|
tool({
|
|
21757
21769
|
name: "list_risks",
|
|
21758
21770
|
title: "Risks, issues and decisions",
|
|
21759
|
-
description: "The RAID register, highest score first: open risks
|
|
21771
|
+
description: "The RAID register, highest score first: open risks and issues, and decisions still to make or standing, unless asked for all (closed, resolved and replaced ones too). Each item says when it was logged and last changed." + DATA_NOTE,
|
|
21760
21772
|
input: object({
|
|
21761
21773
|
harbor: harborArg,
|
|
21762
21774
|
kind: array(_enum([
|
|
@@ -21773,7 +21785,7 @@ const HARBOR_TOOLS = [
|
|
|
21773
21785
|
const { data } = ctx;
|
|
21774
21786
|
const people = byId(data.people);
|
|
21775
21787
|
const project = input.project ? findOne(data.projects, input.project, "project") : null;
|
|
21776
|
-
const list = data.raidItems.filter((r) => (input.includeClosed ||
|
|
21788
|
+
const list = data.raidItems.filter((r) => (input.includeClosed || onLiveRegister(r)) && (!input.kind?.length || input.kind.includes(r.kind)) && (!project || r.projectId === project.id)).sort((a, b) => raidScore(b) - raidScore(a));
|
|
21777
21789
|
return {
|
|
21778
21790
|
total: list.length,
|
|
21779
21791
|
items: list.slice(input.offset, input.offset + input.limit).map((r) => ({
|
|
@@ -21789,7 +21801,9 @@ const HARBOR_TOOLS = [
|
|
|
21789
21801
|
due: dayOf(r.dueDate),
|
|
21790
21802
|
...raidScope(data, r),
|
|
21791
21803
|
...r.mitigation ? { mitigation: r.mitigation } : {},
|
|
21792
|
-
...r.resolution ? { resolution: r.resolution } : {}
|
|
21804
|
+
...r.resolution ? { resolution: r.resolution } : {},
|
|
21805
|
+
createdAt: r.createdAt,
|
|
21806
|
+
updatedAt: r.updatedAt
|
|
21793
21807
|
}))
|
|
21794
21808
|
};
|
|
21795
21809
|
}
|
|
@@ -30011,6 +30025,15 @@ function defaultAction(type) {
|
|
|
30011
30025
|
};
|
|
30012
30026
|
}
|
|
30013
30027
|
|
|
30028
|
+
// src/lib/board-lanes.ts
|
|
30029
|
+
/** Fields a board can be split into lanes by. */
|
|
30030
|
+
function isLaneField(def) {
|
|
30031
|
+
return def.type === "select" || def.type === "multi" || def.type === "person" || def.type === "checkbox" || def.type === "date";
|
|
30032
|
+
}
|
|
30033
|
+
function fieldLane(fieldId) {
|
|
30034
|
+
return `field:${fieldId}`;
|
|
30035
|
+
}
|
|
30036
|
+
|
|
30014
30037
|
// src/tui/views/setup.tsx
|
|
30015
30038
|
/**
|
|
30016
30039
|
* A project's board setup, as the website's board settings have it: where it
|
|
@@ -30077,6 +30100,10 @@ function BoardPanel({ id, width, height, go, active, onClose }) {
|
|
|
30077
30100
|
const people = useHarbor((s) => s.people);
|
|
30078
30101
|
if (!project) return null;
|
|
30079
30102
|
const config = boardConfigOf({ boardConfigs }, id);
|
|
30103
|
+
const lanes = [...LANES, ...(project.customFields ?? []).filter(isLaneField).map((f) => ({
|
|
30104
|
+
value: fieldLane(f.id),
|
|
30105
|
+
label: `By ${f.name}`
|
|
30106
|
+
}))];
|
|
30080
30107
|
const set = (patch) => store.getState().setBoardConfig(id, {
|
|
30081
30108
|
...config,
|
|
30082
30109
|
preset: "custom",
|
|
@@ -30159,15 +30186,15 @@ function BoardPanel({ id, width, height, go, active, onClose }) {
|
|
|
30159
30186
|
...config.columns.map(columnField),
|
|
30160
30187
|
{
|
|
30161
30188
|
label: "Lanes",
|
|
30162
|
-
value:
|
|
30189
|
+
value: lanes.find((l) => l.value === config.swimlane)?.label ?? "None",
|
|
30163
30190
|
edit: {
|
|
30164
30191
|
kind: "pick",
|
|
30165
30192
|
title: "Lanes across the website's board",
|
|
30166
|
-
choices:
|
|
30193
|
+
choices: lanes.map((l) => ({
|
|
30167
30194
|
label: l.label,
|
|
30168
30195
|
value: l.value
|
|
30169
30196
|
})),
|
|
30170
|
-
initial:
|
|
30197
|
+
initial: Math.max(0, lanes.findIndex((l) => l.value === config.swimlane)),
|
|
30171
30198
|
onPick: (v) => set({ swimlane: v })
|
|
30172
30199
|
}
|
|
30173
30200
|
},
|
package/lib/exit.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leaving once what was written has left. Into a pipe, stdout is written
|
|
3
|
+
* asynchronously, so `process.exit()` straight after a big write cut
|
|
4
|
+
* `harbor export harbor | jq` off at 64 KB with no error. A zero-length
|
|
5
|
+
* write's callback runs after every write before it, so waiting on one per
|
|
6
|
+
* stream is enough; the exit after it still stops anything left running.
|
|
7
|
+
*/
|
|
8
|
+
const flushed = (stream) =>
|
|
9
|
+
new Promise((resolve) => {
|
|
10
|
+
if (!stream.writableLength) resolve();
|
|
11
|
+
else stream.write("", () => resolve());
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export async function exitWhenWritten(code, streams = [process.stdout, process.stderr]) {
|
|
15
|
+
process.exitCode = code;
|
|
16
|
+
await Promise.all(streams.map(flushed));
|
|
17
|
+
process.exit(code);
|
|
18
|
+
}
|