plc-cli 0.1.3 → 0.2.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/dist/cli.js +101 -48
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// src/meta.ts
|
|
7
7
|
var PRODUCT_NAME = "Plane Cockpit";
|
|
8
8
|
var BINARY_NAME = "plc";
|
|
9
|
-
var VERSION = true ? "0.
|
|
9
|
+
var VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
10
10
|
var AUTHOR_HANDLE = "@brunoomariano";
|
|
11
11
|
|
|
12
12
|
// src/commands/auth/index.ts
|
|
@@ -233,6 +233,12 @@ var profileSchema = z.strictObject({
|
|
|
233
233
|
// Profile-wide default sort, inherited by any view that does not declare
|
|
234
234
|
// its own `sort`. Same shape as a view's sort.
|
|
235
235
|
sort: sortSpecSchema.optional(),
|
|
236
|
+
// Global state ordering for `sort: state` and the quick-transition
|
|
237
|
+
// navigation. An ordered list of state slugs (matched case-insensitively,
|
|
238
|
+
// whitespace-collapsed): listed states sort first in this order, unlisted
|
|
239
|
+
// ones after by workflow group. Project states are customizable, so this
|
|
240
|
+
// lets one list rank them across every project.
|
|
241
|
+
state_order: z.array(z.string().min(1)).optional(),
|
|
236
242
|
// Profile-wide default column layout, inherited by views without a layout.
|
|
237
243
|
layout: layoutSchema.optional()
|
|
238
244
|
}).optional(),
|
|
@@ -1059,6 +1065,31 @@ function filtersFingerprint(filters) {
|
|
|
1059
1065
|
return createHash("sha256").update(json).digest("hex").slice(0, 16);
|
|
1060
1066
|
}
|
|
1061
1067
|
|
|
1068
|
+
// src/plane/state-rank.ts
|
|
1069
|
+
var STATE_GROUP_RANK = {
|
|
1070
|
+
backlog: 0,
|
|
1071
|
+
unstarted: 1,
|
|
1072
|
+
started: 2,
|
|
1073
|
+
completed: 3,
|
|
1074
|
+
cancelled: 4
|
|
1075
|
+
};
|
|
1076
|
+
var UNLISTED_OFFSET = 1e6;
|
|
1077
|
+
function normalizeStateSlug(name) {
|
|
1078
|
+
return name.trim().toLowerCase().replace(/\s+/g, " ");
|
|
1079
|
+
}
|
|
1080
|
+
function buildStateRank(stateOrder) {
|
|
1081
|
+
const position = /* @__PURE__ */ new Map();
|
|
1082
|
+
(stateOrder ?? []).forEach((slug, index) => {
|
|
1083
|
+
const normalized = normalizeStateSlug(slug);
|
|
1084
|
+
if (!position.has(normalized)) position.set(normalized, index);
|
|
1085
|
+
});
|
|
1086
|
+
return (state) => {
|
|
1087
|
+
const listed = position.get(normalizeStateSlug(state.name));
|
|
1088
|
+
if (listed !== void 0) return listed;
|
|
1089
|
+
return UNLISTED_OFFSET + STATE_GROUP_RANK[state.group];
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1062
1093
|
// src/plane/sort-issues.ts
|
|
1063
1094
|
var PRIORITY_RANK = {
|
|
1064
1095
|
urgent: 0,
|
|
@@ -1067,45 +1098,41 @@ var PRIORITY_RANK = {
|
|
|
1067
1098
|
low: 3,
|
|
1068
1099
|
none: 4
|
|
1069
1100
|
};
|
|
1070
|
-
var STATE_GROUP_RANK = {
|
|
1071
|
-
backlog: 0,
|
|
1072
|
-
unstarted: 1,
|
|
1073
|
-
started: 2,
|
|
1074
|
-
completed: 3,
|
|
1075
|
-
cancelled: 4
|
|
1076
|
-
};
|
|
1077
1101
|
var DEFAULT_SORT = [
|
|
1078
1102
|
{ field: "project", direction: "asc" },
|
|
1079
1103
|
{ field: "priority", direction: "desc" },
|
|
1080
1104
|
{ field: "state", direction: "asc" },
|
|
1081
1105
|
{ field: "updated_at", direction: "desc" }
|
|
1082
1106
|
];
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1107
|
+
function ascComparators(stateRank) {
|
|
1108
|
+
return {
|
|
1109
|
+
project: (a, b) => a.project_identifier.localeCompare(b.project_identifier),
|
|
1110
|
+
priority: (a, b) => PRIORITY_RANK[b.priority] - PRIORITY_RANK[a.priority],
|
|
1111
|
+
state: (a, b) => stateRank(a.state) - stateRank(b.state),
|
|
1112
|
+
created_at: (a, b) => a.created_at.localeCompare(b.created_at),
|
|
1113
|
+
updated_at: (a, b) => a.updated_at.localeCompare(b.updated_at),
|
|
1114
|
+
assign: (a, b) => firstAssignee(a).localeCompare(firstAssignee(b))
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1091
1117
|
function firstAssignee(issue) {
|
|
1092
1118
|
return issue.assignees[0]?.display_name ?? "";
|
|
1093
1119
|
}
|
|
1094
|
-
function compareKey(a, b, field, direction) {
|
|
1120
|
+
function compareKey(a, b, { field, direction }, comparators) {
|
|
1095
1121
|
if (field === "assign") {
|
|
1096
1122
|
const aUnassigned = a.assignees.length === 0;
|
|
1097
1123
|
const bUnassigned = b.assignees.length === 0;
|
|
1098
1124
|
if (aUnassigned !== bUnassigned) return aUnassigned ? 1 : -1;
|
|
1099
1125
|
if (aUnassigned && bUnassigned) return 0;
|
|
1100
1126
|
}
|
|
1101
|
-
const base =
|
|
1127
|
+
const base = comparators[field](a, b);
|
|
1102
1128
|
return direction === "desc" ? -base : base;
|
|
1103
1129
|
}
|
|
1104
|
-
function sortIssues(issues, sort) {
|
|
1130
|
+
function sortIssues(issues, sort, stateOrder) {
|
|
1105
1131
|
if (!sort || sort.length === 0) return issues;
|
|
1132
|
+
const comparators = ascComparators(buildStateRank(stateOrder));
|
|
1106
1133
|
return [...issues].sort((a, b) => {
|
|
1107
|
-
for (const
|
|
1108
|
-
const cmp = compareKey(a, b,
|
|
1134
|
+
for (const key of sort) {
|
|
1135
|
+
const cmp = compareKey(a, b, key, comparators);
|
|
1109
1136
|
if (cmp !== 0) return cmp;
|
|
1110
1137
|
}
|
|
1111
1138
|
return 0;
|
|
@@ -1412,18 +1439,21 @@ var IssuesService = class {
|
|
|
1412
1439
|
* `defaultsSort` is the profile-level `defaults.sort`; the effective sort is
|
|
1413
1440
|
* resolved once as `view.sort ?? defaultsSort ?? DEFAULT_SORT` and applied
|
|
1414
1441
|
* both as the per-project server hint and the authoritative client-side sort.
|
|
1442
|
+
* `stateOrder` is the profile-level `defaults.state_order`, used by the
|
|
1443
|
+
* client-side `state` sort to rank states by the configured slug order.
|
|
1415
1444
|
*/
|
|
1416
1445
|
// `signal` is threaded down to each per-project fetch so the dashboard can
|
|
1417
1446
|
// abort an in-flight refresh (e.g. when a new one starts before the previous
|
|
1418
1447
|
// resolves), preventing requests from piling up and timing out. The positional
|
|
1419
1448
|
// list mirrors the call sites; the cap is waived rather than reshaping both.
|
|
1420
1449
|
// eslint-disable-next-line max-params
|
|
1421
|
-
async list(projectIdentifiers, view, queryLimit, defaultsSort, signal) {
|
|
1450
|
+
async list(projectIdentifiers, view, queryLimit, defaultsSort, stateOrder, signal) {
|
|
1422
1451
|
const { issues, failedProjects } = await this.listResilient(
|
|
1423
1452
|
projectIdentifiers,
|
|
1424
1453
|
view,
|
|
1425
1454
|
queryLimit,
|
|
1426
1455
|
defaultsSort,
|
|
1456
|
+
stateOrder,
|
|
1427
1457
|
signal
|
|
1428
1458
|
);
|
|
1429
1459
|
if (failedProjects.length > 0) {
|
|
@@ -1442,7 +1472,7 @@ var IssuesService = class {
|
|
|
1442
1472
|
*/
|
|
1443
1473
|
// Same positional shape as `list`; the cap is waived for the same reason.
|
|
1444
1474
|
// eslint-disable-next-line max-params
|
|
1445
|
-
async listResilient(projectIdentifiers, view, queryLimit, defaultsSort, signal) {
|
|
1475
|
+
async listResilient(projectIdentifiers, view, queryLimit, defaultsSort, stateOrder, signal) {
|
|
1446
1476
|
const assigneeIds = await this.resolveAssigneeIds(view);
|
|
1447
1477
|
const sort = resolveSort(view?.sort, defaultsSort);
|
|
1448
1478
|
const effectiveView = view ? { ...view, sort } : { name: "", sort };
|
|
@@ -1461,7 +1491,7 @@ var IssuesService = class {
|
|
|
1461
1491
|
const byGroup = refineByStateGroup(fetched, view?.filters?.state_group);
|
|
1462
1492
|
const byState = refineByStateSearch(byGroup, view?.filters);
|
|
1463
1493
|
const byAssignee = refineByAssignee(byState, assigneeIds);
|
|
1464
|
-
const sorted = sortIssues(byAssignee, sort);
|
|
1494
|
+
const sorted = sortIssues(byAssignee, sort, stateOrder);
|
|
1465
1495
|
const issues = queryLimit !== void 0 ? sorted.slice(0, queryLimit) : sorted;
|
|
1466
1496
|
return { issues, failedProjects };
|
|
1467
1497
|
}
|
|
@@ -2413,7 +2443,8 @@ function registerIssue(program2) {
|
|
|
2413
2443
|
projects,
|
|
2414
2444
|
view,
|
|
2415
2445
|
limit ?? view?.query_limit,
|
|
2416
|
-
ctx.runtime.profile.defaults?.sort
|
|
2446
|
+
ctx.runtime.profile.defaults?.sort,
|
|
2447
|
+
ctx.runtime.profile.defaults?.state_order
|
|
2417
2448
|
);
|
|
2418
2449
|
process.stdout.write(renderIssues(issues, format, ctx.theme));
|
|
2419
2450
|
process.stdout.write("\n");
|
|
@@ -2930,6 +2961,16 @@ var PRIORITY_LETTER = {
|
|
|
2930
2961
|
low: "L",
|
|
2931
2962
|
none: "\xB7"
|
|
2932
2963
|
};
|
|
2964
|
+
var SORT_FIELD_COLUMN = {
|
|
2965
|
+
priority: "priority",
|
|
2966
|
+
state: "state",
|
|
2967
|
+
assign: "assign"
|
|
2968
|
+
};
|
|
2969
|
+
function sortIndicator(column, sort) {
|
|
2970
|
+
const primary = sort?.[0];
|
|
2971
|
+
if (!primary || SORT_FIELD_COLUMN[primary.field] !== column) return "";
|
|
2972
|
+
return primary.direction === "asc" ? " \u2191" : " \u2193";
|
|
2973
|
+
}
|
|
2933
2974
|
function computeViewport(total, selected, rows, previousStart = 0) {
|
|
2934
2975
|
if (rows <= 0 || total === 0) return { start: 0, end: 0 };
|
|
2935
2976
|
if (rows >= total) return { start: 0, end: total };
|
|
@@ -2964,10 +3005,16 @@ function IssueList(props) {
|
|
|
2964
3005
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "round", paddingX: 1, flexGrow: 1, children: [
|
|
2965
3006
|
/* @__PURE__ */ jsxs4(Box4, { columnGap: 1, children: [
|
|
2966
3007
|
/* @__PURE__ */ jsx5(Box4, { ...cell("key", cols.keyWidth), children: /* @__PURE__ */ jsx5(Text4, { bold: true, children: alignText("KEY", cols.keyWidth, cols.align.key) }) }),
|
|
2967
|
-
/* @__PURE__ */ jsx5(Box4, { ...cell("priority", cols.priorityWidth), children: /* @__PURE__ */ jsx5(Text4, { bold: true, children: cols.compactPriority ? "PR" :
|
|
2968
|
-
cols.showState ? /* @__PURE__ */ jsx5(Box4, { ...cell("state", cols.stateWidth), children: /* @__PURE__ */
|
|
3008
|
+
/* @__PURE__ */ jsx5(Box4, { ...cell("priority", cols.priorityWidth), children: /* @__PURE__ */ jsx5(Text4, { bold: true, children: cols.compactPriority ? "PR" : `PRIORITY${sortIndicator("priority", props.sort)}` }) }),
|
|
3009
|
+
cols.showState ? /* @__PURE__ */ jsx5(Box4, { ...cell("state", cols.stateWidth), children: /* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
|
|
3010
|
+
"STATE",
|
|
3011
|
+
sortIndicator("state", props.sort)
|
|
3012
|
+
] }) }) : null,
|
|
2969
3013
|
/* @__PURE__ */ jsx5(Box4, { ...cell("title", cols.title), children: /* @__PURE__ */ jsx5(Text4, { bold: true, children: "TITLE" }) }),
|
|
2970
|
-
cols.showAssign ? /* @__PURE__ */ jsx5(Box4, { ...cell("assign", cols.assignWidth), children: /* @__PURE__ */
|
|
3014
|
+
cols.showAssign ? /* @__PURE__ */ jsx5(Box4, { ...cell("assign", cols.assignWidth), children: /* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
|
|
3015
|
+
"ASSIGN",
|
|
3016
|
+
sortIndicator("assign", props.sort)
|
|
3017
|
+
] }) }) : null
|
|
2971
3018
|
] }),
|
|
2972
3019
|
hiddenAbove > 0 ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
2973
3020
|
"\u2191 ",
|
|
@@ -4372,7 +4419,10 @@ var EMPTY_VIEW_DATA = {
|
|
|
4372
4419
|
failedProjects: []
|
|
4373
4420
|
};
|
|
4374
4421
|
function useViewsData(opts) {
|
|
4375
|
-
const { views, issuesService,
|
|
4422
|
+
const { views, issuesService, defaults, logger } = opts;
|
|
4423
|
+
const defaultProjects = defaults?.projects;
|
|
4424
|
+
const defaultsSort = defaults?.sort;
|
|
4425
|
+
const stateOrder = defaults?.state_order;
|
|
4376
4426
|
const [byView, setByView] = useState6(() => views.map(() => EMPTY_VIEW_DATA));
|
|
4377
4427
|
const viewsRef = useRef4(views);
|
|
4378
4428
|
viewsRef.current = views;
|
|
@@ -4414,6 +4464,7 @@ function useViewsData(opts) {
|
|
|
4414
4464
|
view,
|
|
4415
4465
|
view.query_limit ?? 100,
|
|
4416
4466
|
defaultsSort,
|
|
4467
|
+
stateOrder,
|
|
4417
4468
|
controller.signal
|
|
4418
4469
|
);
|
|
4419
4470
|
patch(viewIdx, {
|
|
@@ -4446,7 +4497,7 @@ function useViewsData(opts) {
|
|
|
4446
4497
|
}
|
|
4447
4498
|
}
|
|
4448
4499
|
},
|
|
4449
|
-
[issuesService, defaultProjects, defaultsSort, logger, patch]
|
|
4500
|
+
[issuesService, defaultProjects, defaultsSort, stateOrder, logger, patch]
|
|
4450
4501
|
);
|
|
4451
4502
|
const refreshAll = useCallback4(() => {
|
|
4452
4503
|
const indices = viewsRef.current.map((_, idx) => idx);
|
|
@@ -4534,23 +4585,17 @@ function useIssueFilter(opts) {
|
|
|
4534
4585
|
import { useCallback as useCallback5, useState as useState9 } from "react";
|
|
4535
4586
|
|
|
4536
4587
|
// src/plane/state-order.ts
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
"unstarted",
|
|
4540
|
-
"started",
|
|
4541
|
-
"completed",
|
|
4542
|
-
"cancelled"
|
|
4543
|
-
];
|
|
4544
|
-
function orderStates(states) {
|
|
4588
|
+
function orderStates(states, stateOrder) {
|
|
4589
|
+
const rank = buildStateRank(stateOrder);
|
|
4545
4590
|
return states.map((state, index) => ({ state, index })).sort((a, b) => {
|
|
4546
|
-
const
|
|
4547
|
-
const
|
|
4548
|
-
if (
|
|
4591
|
+
const ra = rank(a.state);
|
|
4592
|
+
const rb = rank(b.state);
|
|
4593
|
+
if (ra !== rb) return ra - rb;
|
|
4549
4594
|
return a.index - b.index;
|
|
4550
4595
|
}).map((entry) => entry.state);
|
|
4551
4596
|
}
|
|
4552
|
-
function neighbourState(states, currentId, direction) {
|
|
4553
|
-
const ordered = orderStates(states);
|
|
4597
|
+
function neighbourState(states, currentId, direction, stateOrder) {
|
|
4598
|
+
const ordered = orderStates(states, stateOrder);
|
|
4554
4599
|
const idx = ordered.findIndex((s) => s.id === currentId);
|
|
4555
4600
|
if (idx < 0) return void 0;
|
|
4556
4601
|
const target = idx + direction;
|
|
@@ -4581,7 +4626,8 @@ function useQuickTransition(opts) {
|
|
|
4581
4626
|
if (!issue) return;
|
|
4582
4627
|
try {
|
|
4583
4628
|
const states = await ctx.states.list(projectOf(issue));
|
|
4584
|
-
const
|
|
4629
|
+
const stateOrder = ctx.runtime.profile.defaults?.state_order;
|
|
4630
|
+
const next = neighbourState(states, issue.state.id, direction, stateOrder);
|
|
4585
4631
|
if (!next) {
|
|
4586
4632
|
setMessage(`${issue.key}: already at the ${direction === 1 ? "last" : "first"} state`);
|
|
4587
4633
|
return;
|
|
@@ -4847,6 +4893,12 @@ function overlayStatusPosition(opts) {
|
|
|
4847
4893
|
if (opts.isDetail && !opts.current) return void 0;
|
|
4848
4894
|
return opts.listPosition;
|
|
4849
4895
|
}
|
|
4896
|
+
function resolveViewPresentation(view, defaults) {
|
|
4897
|
+
return {
|
|
4898
|
+
layout: resolveLayout(view?.layout, defaults?.layout),
|
|
4899
|
+
sort: resolveSort(view?.sort, defaults?.sort)
|
|
4900
|
+
};
|
|
4901
|
+
}
|
|
4850
4902
|
function Dashboard({ ctx, logger }) {
|
|
4851
4903
|
const { exit } = useApp();
|
|
4852
4904
|
const { rows: terminalRows, columns: terminalCols } = useTerminalSize();
|
|
@@ -4863,16 +4915,15 @@ function Dashboard({ ctx, logger }) {
|
|
|
4863
4915
|
const [panel, setPanel] = useState11("list");
|
|
4864
4916
|
const [helpOpen, setHelpOpen] = useState11(false);
|
|
4865
4917
|
const activeView = views[viewIdx];
|
|
4866
|
-
const activeLayout = useMemo6(
|
|
4867
|
-
() =>
|
|
4918
|
+
const { layout: activeLayout, sort: activeSort } = useMemo6(
|
|
4919
|
+
() => resolveViewPresentation(activeView, ctx.runtime.profile.defaults),
|
|
4868
4920
|
[activeView, ctx]
|
|
4869
4921
|
);
|
|
4870
4922
|
const selectedKeyRef = React7.useRef(void 0);
|
|
4871
4923
|
const viewsData = useViewsData({
|
|
4872
4924
|
views,
|
|
4873
4925
|
issuesService: ctx.issues,
|
|
4874
|
-
|
|
4875
|
-
defaultsSort: ctx.runtime.profile.defaults?.sort,
|
|
4926
|
+
defaults: ctx.runtime.profile.defaults,
|
|
4876
4927
|
logger
|
|
4877
4928
|
});
|
|
4878
4929
|
const active = viewsData.byView[viewIdx] ?? {
|
|
@@ -5198,6 +5249,7 @@ function Dashboard({ ctx, logger }) {
|
|
|
5198
5249
|
filtering,
|
|
5199
5250
|
viewportRows,
|
|
5200
5251
|
layout: activeLayout,
|
|
5252
|
+
sort: activeSort,
|
|
5201
5253
|
loading,
|
|
5202
5254
|
statusBar: /* @__PURE__ */ jsx13(StatusBar, { ...statusBarBase, loading, position: listPosition })
|
|
5203
5255
|
}
|
|
@@ -5225,6 +5277,7 @@ function ListLayout(props) {
|
|
|
5225
5277
|
viewportRows: props.viewportRows,
|
|
5226
5278
|
width: props.narrow ? props.width : props.width - SIDE_PANEL_WIDTH,
|
|
5227
5279
|
layout: props.layout,
|
|
5280
|
+
sort: props.sort,
|
|
5228
5281
|
loading: props.loading
|
|
5229
5282
|
}
|
|
5230
5283
|
),
|