@islamihab/kds 0.2.0 → 0.2.2
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/index.js +352 -66
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -14450,7 +14450,12 @@ var isHidden = (field) => field.meta()?.hidden === true;
|
|
|
14450
14450
|
var isNegatable = (field) => field.meta()?.negatable === true;
|
|
14451
14451
|
var isHelpFlag = (arg) => arg === "-h" || arg === "--help";
|
|
14452
14452
|
var valuePlaceholder = (field) => {
|
|
14453
|
-
|
|
14453
|
+
let base = baseType(field);
|
|
14454
|
+
if (base instanceof exports_external.ZodArray) {
|
|
14455
|
+
const element = base.element;
|
|
14456
|
+
if (element instanceof exports_external.ZodType)
|
|
14457
|
+
base = element;
|
|
14458
|
+
}
|
|
14454
14459
|
if (base instanceof exports_external.ZodBoolean)
|
|
14455
14460
|
return "";
|
|
14456
14461
|
if (base instanceof exports_external.ZodEnum)
|
|
@@ -14496,8 +14501,10 @@ var parseArgs = ({
|
|
|
14496
14501
|
}) => {
|
|
14497
14502
|
const options = {};
|
|
14498
14503
|
for (const { key, field, short, negatedKey } of optionsEntries) {
|
|
14499
|
-
const
|
|
14500
|
-
|
|
14504
|
+
const base = baseType(field);
|
|
14505
|
+
const type = base instanceof exports_external.ZodBoolean ? "boolean" : "string";
|
|
14506
|
+
const multiple = base instanceof exports_external.ZodArray ? true : undefined;
|
|
14507
|
+
options[key] = short ? { short, type, multiple } : { type, multiple };
|
|
14501
14508
|
if (negatedKey)
|
|
14502
14509
|
options[negatedKey] = { type: "boolean" };
|
|
14503
14510
|
}
|
|
@@ -14587,7 +14594,7 @@ import { join } from "path";
|
|
|
14587
14594
|
// package.json
|
|
14588
14595
|
var package_default = {
|
|
14589
14596
|
name: "cli",
|
|
14590
|
-
version: "0.2.
|
|
14597
|
+
version: "0.2.2",
|
|
14591
14598
|
private: true,
|
|
14592
14599
|
type: "module",
|
|
14593
14600
|
bin: {
|
|
@@ -15159,10 +15166,12 @@ var ISSUE_CREATE_DISPOSITIONS = [
|
|
|
15159
15166
|
];
|
|
15160
15167
|
var ISSUE_ESTIMATES = [1, 2, 3, 5, 8];
|
|
15161
15168
|
var ISSUE_TERMINAL_STATUSES = ["done", "canceled"];
|
|
15169
|
+
var ISSUE_RELATION_KINDS = ["blocks", "blocked_by", "duplicate_of", "duplicated_by", "relates_to"];
|
|
15162
15170
|
var PROJECT_STATUSES = ["planned", "in_progress", "paused", "completed", "canceled"];
|
|
15163
15171
|
var PROJECT_HEALTHS = ["on_track", "at_risk", "off_track"];
|
|
15164
15172
|
var ISSUE_IDENTIFIER_PREFIX = "KAI";
|
|
15165
15173
|
var ISSUE_LIST_PAGE_SIZE = 50;
|
|
15174
|
+
var MAX_ISSUE_ATTACHMENT_BYTES = 1e7;
|
|
15166
15175
|
var ISSUE_DUE_DATE_MODES = ["overdue", "due_today", "due_soon", "no_due_date"];
|
|
15167
15176
|
var issueIsOpen = (status) => !ISSUE_TERMINAL_STATUSES.some((terminal) => terminal === status);
|
|
15168
15177
|
var MAX_PROJECT_REPO_LENGTH = 200;
|
|
@@ -17814,6 +17823,117 @@ var auth = group({
|
|
|
17814
17823
|
commands: [login, logout, status]
|
|
17815
17824
|
});
|
|
17816
17825
|
|
|
17826
|
+
// src/lib/activity.ts
|
|
17827
|
+
var SOURCE_LABELS = { github: "GitHub" };
|
|
17828
|
+
var issueActivityLine = (detail) => {
|
|
17829
|
+
const label = detail.kind.replaceAll("_", " ");
|
|
17830
|
+
switch (detail.kind) {
|
|
17831
|
+
case "created":
|
|
17832
|
+
case "description_changed":
|
|
17833
|
+
return label;
|
|
17834
|
+
case "title_changed":
|
|
17835
|
+
case "status_changed":
|
|
17836
|
+
case "priority_changed":
|
|
17837
|
+
case "disposition_changed":
|
|
17838
|
+
case "estimate_changed":
|
|
17839
|
+
case "due_date_changed":
|
|
17840
|
+
case "branch_changed":
|
|
17841
|
+
case "pr_changed":
|
|
17842
|
+
case "project_changed":
|
|
17843
|
+
case "milestone_changed":
|
|
17844
|
+
case "parent_changed":
|
|
17845
|
+
return `${label}: ${detail.from ?? "none"} \u2192 ${detail.to ?? "none"}`;
|
|
17846
|
+
case "label_added":
|
|
17847
|
+
case "label_removed":
|
|
17848
|
+
case "attachment_added":
|
|
17849
|
+
case "attachment_removed":
|
|
17850
|
+
return `${label}: ${detail.name}`;
|
|
17851
|
+
case "child_added":
|
|
17852
|
+
case "child_removed":
|
|
17853
|
+
return `${label}: ${detail.identifier}`;
|
|
17854
|
+
case "relation_added":
|
|
17855
|
+
case "relation_removed":
|
|
17856
|
+
return `${label}: ${detail.relation.replaceAll("_", " ")} ${detail.identifier}`;
|
|
17857
|
+
}
|
|
17858
|
+
};
|
|
17859
|
+
var projectActivityLine = (detail) => {
|
|
17860
|
+
const label = detail.kind.replaceAll("_", " ");
|
|
17861
|
+
switch (detail.kind) {
|
|
17862
|
+
case "created":
|
|
17863
|
+
case "summary_changed":
|
|
17864
|
+
case "description_changed":
|
|
17865
|
+
case "milestones_reordered":
|
|
17866
|
+
return label;
|
|
17867
|
+
case "name_changed":
|
|
17868
|
+
case "status_changed":
|
|
17869
|
+
case "health_changed":
|
|
17870
|
+
case "target_date_changed":
|
|
17871
|
+
case "repo_changed":
|
|
17872
|
+
case "milestone_renamed":
|
|
17873
|
+
return `${label}: ${detail.from ?? "none"} \u2192 ${detail.to ?? "none"}`;
|
|
17874
|
+
case "milestone_added":
|
|
17875
|
+
case "milestone_removed":
|
|
17876
|
+
case "milestone_description_changed":
|
|
17877
|
+
return `${label}: ${detail.name}`;
|
|
17878
|
+
case "milestone_target_date_changed":
|
|
17879
|
+
return `${label}: ${detail.name} ${detail.from ?? "none"} \u2192 ${detail.to ?? "none"}`;
|
|
17880
|
+
case "issue_added":
|
|
17881
|
+
case "issue_removed":
|
|
17882
|
+
return `${label}: ${detail.identifier}`;
|
|
17883
|
+
}
|
|
17884
|
+
};
|
|
17885
|
+
|
|
17886
|
+
// src/lib/output.ts
|
|
17887
|
+
var printTable = (rows) => {
|
|
17888
|
+
if (rows.length === 0)
|
|
17889
|
+
return;
|
|
17890
|
+
const columnCount = Math.max(...rows.map((row) => row.length));
|
|
17891
|
+
const widths = Array.from({ length: columnCount }, (_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length)));
|
|
17892
|
+
for (const row of rows) {
|
|
17893
|
+
console.log(row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" ").trimEnd());
|
|
17894
|
+
}
|
|
17895
|
+
};
|
|
17896
|
+
|
|
17897
|
+
// src/commands/inbox/list.ts
|
|
17898
|
+
var inboxOptions = {
|
|
17899
|
+
all: exports_external.boolean().default(false).describe("Include events already marked seen"),
|
|
17900
|
+
json: exports_external.boolean().default(false).describe("Print as JSON")
|
|
17901
|
+
};
|
|
17902
|
+
var runInbox = async (options) => {
|
|
17903
|
+
const client3 = await backendClient();
|
|
17904
|
+
const inbox = await client3.query(api2.inbox.list, {});
|
|
17905
|
+
const targetLastSeen = new Map(inbox.targetLastSeen.map((state) => [state.targetId, state.lastSeenAt]));
|
|
17906
|
+
const events = options.all ? inbox.events : inbox.events.filter((event) => {
|
|
17907
|
+
const targetId = event.type === "issue" ? event.issue.id : event.project.id;
|
|
17908
|
+
return event.at > Math.max(inbox.lastSeenAt, targetLastSeen.get(targetId) ?? 0);
|
|
17909
|
+
});
|
|
17910
|
+
if (options.json)
|
|
17911
|
+
return console.log(JSON.stringify({ ...inbox, events }, null, 2));
|
|
17912
|
+
if (events.length === 0) {
|
|
17913
|
+
if (options.all)
|
|
17914
|
+
console.log("Nothing in the inbox.");
|
|
17915
|
+
else
|
|
17916
|
+
console.log(inbox.truncated ? "Nothing unseen in the newest events shown." : "Inbox zero.");
|
|
17917
|
+
if (!options.all && inbox.events.length > 0)
|
|
17918
|
+
console.log("Pass --all to reprint seen events.");
|
|
17919
|
+
return;
|
|
17920
|
+
}
|
|
17921
|
+
printTable([
|
|
17922
|
+
["WHEN", "ACTOR", "WHERE", "EVENT"],
|
|
17923
|
+
...events.map((event) => [
|
|
17924
|
+
new Date(event.at).toISOString().slice(0, 16).replace("T", " "),
|
|
17925
|
+
SOURCE_LABELS[event.via],
|
|
17926
|
+
event.type === "issue" ? event.issue.identifier : event.project.name,
|
|
17927
|
+
event.type === "issue" ? issueActivityLine(event.detail) : projectActivityLine(event.detail)
|
|
17928
|
+
])
|
|
17929
|
+
]);
|
|
17930
|
+
if (inbox.truncated)
|
|
17931
|
+
console.log(`
|
|
17932
|
+
(older events truncated)`);
|
|
17933
|
+
if (!options.all)
|
|
17934
|
+
console.log("\nRun `kds inbox seen` once these are handled.");
|
|
17935
|
+
};
|
|
17936
|
+
|
|
17817
17937
|
// src/lib/input.ts
|
|
17818
17938
|
var assertSize = (size) => {
|
|
17819
17939
|
if (size > MAX_PAGE_HTML_BYTES)
|
|
@@ -17924,6 +18044,20 @@ var repoProject = async (client3) => {
|
|
|
17924
18044
|
throw new Error(`No project is connected to ${repo}. Connect one on the project in the dashboard.`);
|
|
17925
18045
|
return project;
|
|
17926
18046
|
};
|
|
18047
|
+
var resolveIssueLabels = async (client3, names) => {
|
|
18048
|
+
if (names.length === 0)
|
|
18049
|
+
return [];
|
|
18050
|
+
const labels = await client3.query(api2.issueLabels.list, {});
|
|
18051
|
+
return names.map((name) => {
|
|
18052
|
+
const wanted = name.trim().toLowerCase();
|
|
18053
|
+
const label = labels.find((candidate) => candidate.name.toLowerCase() === wanted);
|
|
18054
|
+
if (!label) {
|
|
18055
|
+
const available = labels.map((candidate) => candidate.name).join(", ");
|
|
18056
|
+
throw new Error(available ? `No label named "${name}". The labels are: ${available}.` : "There are no labels yet.");
|
|
18057
|
+
}
|
|
18058
|
+
return label;
|
|
18059
|
+
});
|
|
18060
|
+
};
|
|
17927
18061
|
var resolveMilestone = async (client3, projectId, name) => {
|
|
17928
18062
|
const milestones = await client3.query(api2.milestones.listByProject, { projectId, today: localToday() });
|
|
17929
18063
|
const wanted = name.trim().toLowerCase();
|
|
@@ -17935,6 +18069,83 @@ var resolveMilestone = async (client3, projectId, name) => {
|
|
|
17935
18069
|
return milestone;
|
|
17936
18070
|
};
|
|
17937
18071
|
|
|
18072
|
+
// src/commands/inbox/seen.ts
|
|
18073
|
+
var seen = command({
|
|
18074
|
+
name: "seen",
|
|
18075
|
+
description: "Mark one issue, or the whole inbox, as seen",
|
|
18076
|
+
positionals: {
|
|
18077
|
+
id: exports_external.string().optional().describe("Issue identifier, number, or URL")
|
|
18078
|
+
},
|
|
18079
|
+
run: async ({ positionals: { id } }) => {
|
|
18080
|
+
const client3 = await backendClient();
|
|
18081
|
+
const issue2 = id === undefined ? undefined : await resolveIssue(client3, id);
|
|
18082
|
+
await client3.mutation(api2.inbox.markSeen, { targetId: issue2?._id });
|
|
18083
|
+
console.log(issue2 ? `${issue2.identifier} marked seen.` : "Inbox marked seen.");
|
|
18084
|
+
}
|
|
18085
|
+
});
|
|
18086
|
+
|
|
18087
|
+
// src/commands/inbox/index.ts
|
|
18088
|
+
var inbox = group({
|
|
18089
|
+
name: "inbox",
|
|
18090
|
+
description: "Automated activity across issues and projects, unseen first",
|
|
18091
|
+
options: inboxOptions,
|
|
18092
|
+
run: async ({ options }) => await runInbox(options),
|
|
18093
|
+
commands: [seen]
|
|
18094
|
+
});
|
|
18095
|
+
|
|
18096
|
+
// src/lib/attachments.ts
|
|
18097
|
+
import { basename } from "path";
|
|
18098
|
+
var attachFiles = async (client3, issueId, paths) => {
|
|
18099
|
+
if (paths.length === 0)
|
|
18100
|
+
return [];
|
|
18101
|
+
const files = paths.map((path) => ({ path, file: Bun.file(path) }));
|
|
18102
|
+
for (const { path, file: file2 } of files) {
|
|
18103
|
+
if (!await file2.exists())
|
|
18104
|
+
throw new Error(`No file at ${path}.`);
|
|
18105
|
+
if (file2.size <= 0)
|
|
18106
|
+
throw new Error(`${path} is empty; an attachment cannot be.`);
|
|
18107
|
+
if (file2.size > MAX_ISSUE_ATTACHMENT_BYTES) {
|
|
18108
|
+
throw new Error(`${path} is too large (${file2.size} bytes; max ${MAX_ISSUE_ATTACHMENT_BYTES}).`);
|
|
18109
|
+
}
|
|
18110
|
+
}
|
|
18111
|
+
const created = new Set;
|
|
18112
|
+
try {
|
|
18113
|
+
for (const { path, file: file2 } of files) {
|
|
18114
|
+
const uploadUrl = await client3.mutation(api2.issueAttachments.generateUploadUrl, { issueId });
|
|
18115
|
+
const response = await fetch(uploadUrl, {
|
|
18116
|
+
method: "POST",
|
|
18117
|
+
headers: { "Content-Type": file2.type },
|
|
18118
|
+
body: file2
|
|
18119
|
+
});
|
|
18120
|
+
if (!response.ok)
|
|
18121
|
+
throw new Error(`Uploading ${path} failed (${response.status}).`);
|
|
18122
|
+
const storageId = exports_external.custom((value) => typeof value === "string");
|
|
18123
|
+
const body = exports_external.object({ storageId }).safeParse(await response.json());
|
|
18124
|
+
if (!body.success)
|
|
18125
|
+
throw new Error("The upload returned no file reference.");
|
|
18126
|
+
created.add(await client3.action(api2.issueAttachments.create, {
|
|
18127
|
+
issueId,
|
|
18128
|
+
storageId: body.data.storageId,
|
|
18129
|
+
name: basename(path)
|
|
18130
|
+
}));
|
|
18131
|
+
}
|
|
18132
|
+
} catch (error51) {
|
|
18133
|
+
for (const id of created) {
|
|
18134
|
+
await client3.mutation(api2.issueAttachments.remove, { id }).catch(() => {
|
|
18135
|
+
console.error("An attachment from this failed batch could not be removed; check the dashboard.");
|
|
18136
|
+
});
|
|
18137
|
+
}
|
|
18138
|
+
throw error51;
|
|
18139
|
+
}
|
|
18140
|
+
const attachments = await client3.query(api2.issueAttachments.list, { issueId });
|
|
18141
|
+
return attachments.filter((attachment) => created.has(attachment._id));
|
|
18142
|
+
};
|
|
18143
|
+
var printAttachments = (attachments) => {
|
|
18144
|
+
for (const attachment of attachments) {
|
|
18145
|
+
console.log(`Attached ${attachment.name}${attachment.url ? ` \u2014 ${attachment.url}` : ""}`);
|
|
18146
|
+
}
|
|
18147
|
+
};
|
|
18148
|
+
|
|
17938
18149
|
// src/commands/issues/comment.ts
|
|
17939
18150
|
var comment = command({
|
|
17940
18151
|
name: "comment",
|
|
@@ -17943,9 +18154,13 @@ var comment = command({
|
|
|
17943
18154
|
id: exports_external.string().describe("Issue identifier, number, or URL"),
|
|
17944
18155
|
body: exports_external.string().describe("Comment markdown, or - for stdin")
|
|
17945
18156
|
},
|
|
17946
|
-
|
|
18157
|
+
options: {
|
|
18158
|
+
attach: exports_external.array(exports_external.string()).optional().describe("Attach a file to the issue (repeatable)").meta({ short: "a" })
|
|
18159
|
+
},
|
|
18160
|
+
run: async ({ positionals: { id, body }, options: { attach } }) => {
|
|
17947
18161
|
const client3 = await backendClient();
|
|
17948
18162
|
const issue2 = await resolveIssue(client3, id);
|
|
18163
|
+
printAttachments(await attachFiles(client3, issue2._id, attach ?? []));
|
|
17949
18164
|
await client3.mutation(api2.issueComments.create, { issueId: issue2._id, bodyMarkdown: await readTextOption(body) });
|
|
17950
18165
|
console.log(`Commented on ${issue2.identifier}.`);
|
|
17951
18166
|
}
|
|
@@ -17993,35 +18208,6 @@ var create = command({
|
|
|
17993
18208
|
});
|
|
17994
18209
|
|
|
17995
18210
|
// src/commands/issues/get.ts
|
|
17996
|
-
var activityLine = (detail) => {
|
|
17997
|
-
const label = detail.kind.replaceAll("_", " ");
|
|
17998
|
-
switch (detail.kind) {
|
|
17999
|
-
case "created":
|
|
18000
|
-
case "description_changed":
|
|
18001
|
-
return label;
|
|
18002
|
-
case "title_changed":
|
|
18003
|
-
case "status_changed":
|
|
18004
|
-
case "priority_changed":
|
|
18005
|
-
case "disposition_changed":
|
|
18006
|
-
case "estimate_changed":
|
|
18007
|
-
case "due_date_changed":
|
|
18008
|
-
case "project_changed":
|
|
18009
|
-
case "milestone_changed":
|
|
18010
|
-
case "parent_changed":
|
|
18011
|
-
return `${label}: ${detail.from ?? "none"} \u2192 ${detail.to ?? "none"}`;
|
|
18012
|
-
case "label_added":
|
|
18013
|
-
case "label_removed":
|
|
18014
|
-
case "attachment_added":
|
|
18015
|
-
case "attachment_removed":
|
|
18016
|
-
return `${label}: ${detail.name}`;
|
|
18017
|
-
case "child_added":
|
|
18018
|
-
case "child_removed":
|
|
18019
|
-
return `${label}: ${detail.identifier}`;
|
|
18020
|
-
case "relation_added":
|
|
18021
|
-
case "relation_removed":
|
|
18022
|
-
return `${label}: ${detail.relation.replaceAll("_", " ")} ${detail.identifier}`;
|
|
18023
|
-
}
|
|
18024
|
-
};
|
|
18025
18211
|
var get = command({
|
|
18026
18212
|
name: "get",
|
|
18027
18213
|
description: "Show an issue: properties, description, and its feed",
|
|
@@ -18034,9 +18220,13 @@ var get = command({
|
|
|
18034
18220
|
run: async ({ positionals: { id }, options: { json: json2 } }) => {
|
|
18035
18221
|
const client3 = await backendClient();
|
|
18036
18222
|
const issue2 = await resolveIssue(client3, id);
|
|
18037
|
-
const feed = await
|
|
18223
|
+
const [feed, relations, attachments] = await Promise.all([
|
|
18224
|
+
client3.query(api2.issues.feed, { id: issue2._id }),
|
|
18225
|
+
client3.query(api2.issues.relations, { id: issue2._id }),
|
|
18226
|
+
client3.query(api2.issueAttachments.list, { issueId: issue2._id })
|
|
18227
|
+
]);
|
|
18038
18228
|
if (json2)
|
|
18039
|
-
return console.log(JSON.stringify({ ...issue2, feed }, null, 2));
|
|
18229
|
+
return console.log(JSON.stringify({ ...issue2, relations, attachments, feed }, null, 2));
|
|
18040
18230
|
const project = issue2.projectId ? await client3.query(api2.projects.get, { id: issue2.projectId, today: localToday() }) : null;
|
|
18041
18231
|
const milestone = issue2.milestoneId ? await client3.query(api2.milestones.get, { id: issue2.milestoneId, today: localToday() }) : null;
|
|
18042
18232
|
console.log(`${issue2.identifier} ${issue2.title}`);
|
|
@@ -18045,6 +18235,10 @@ var get = command({
|
|
|
18045
18235
|
console.log(`Estimate: ${issue2.estimate}`);
|
|
18046
18236
|
if (issue2.dueDate)
|
|
18047
18237
|
console.log(`Due: ${issue2.dueDate}`);
|
|
18238
|
+
if (issue2.branch)
|
|
18239
|
+
console.log(`Branch: ${issue2.branch}`);
|
|
18240
|
+
if (issue2.prUrl)
|
|
18241
|
+
console.log(`PR: ${issue2.prUrl}`);
|
|
18048
18242
|
if (project)
|
|
18049
18243
|
console.log(`Project: ${project.name}${milestone ? ` \xB7 Milestone: ${milestone.name}` : ""}`);
|
|
18050
18244
|
if (issue2.parent)
|
|
@@ -18053,6 +18247,23 @@ var get = command({
|
|
|
18053
18247
|
console.log(`Labels: ${issue2.labels.map((label) => label.name).join(", ")}`);
|
|
18054
18248
|
if (issue2.children.total > 0)
|
|
18055
18249
|
console.log(`Sub-issues: ${issue2.children.done}/${issue2.children.total} done`);
|
|
18250
|
+
const related = (entries) => entries.map((entry) => `${entry.issue.identifier} (${entry.issue.status})`).join(", ");
|
|
18251
|
+
if (relations.blockedBy.length > 0)
|
|
18252
|
+
console.log(`Blocked by: ${related(relations.blockedBy)}`);
|
|
18253
|
+
if (relations.blocks.length > 0)
|
|
18254
|
+
console.log(`Blocks: ${related(relations.blocks)}`);
|
|
18255
|
+
if (relations.duplicateOf)
|
|
18256
|
+
console.log(`Duplicate of: ${related([relations.duplicateOf])}`);
|
|
18257
|
+
if (relations.duplicates.length > 0)
|
|
18258
|
+
console.log(`Duplicated by: ${related(relations.duplicates)}`);
|
|
18259
|
+
if (relations.relatesTo.length > 0)
|
|
18260
|
+
console.log(`Related: ${related(relations.relatesTo)}`);
|
|
18261
|
+
if (attachments.length > 0) {
|
|
18262
|
+
console.log("Attachments:");
|
|
18263
|
+
for (const attachment of attachments) {
|
|
18264
|
+
console.log(` ${attachment.name} \u2014 ${attachment.url ?? "unavailable"}`);
|
|
18265
|
+
}
|
|
18266
|
+
}
|
|
18056
18267
|
if (issue2.descriptionMarkdown)
|
|
18057
18268
|
console.log(`
|
|
18058
18269
|
${issue2.descriptionMarkdown}`);
|
|
@@ -18064,7 +18275,8 @@ Feed:`);
|
|
|
18064
18275
|
for (const event of feed.events) {
|
|
18065
18276
|
const at = new Date(event.at).toISOString().slice(0, 16).replace("T", " ");
|
|
18066
18277
|
if (event.type === "activity") {
|
|
18067
|
-
|
|
18278
|
+
const via = event.via === undefined ? "" : ` (via ${SOURCE_LABELS[event.via]})`;
|
|
18279
|
+
console.log(`${at} ${issueActivityLine(event.detail)}${via}`);
|
|
18068
18280
|
} else {
|
|
18069
18281
|
console.log(`${at} comment${event.editedAt ? " (edited)" : ""}:`);
|
|
18070
18282
|
for (const line of event.bodyMarkdown.split(`
|
|
@@ -18075,17 +18287,6 @@ Feed:`);
|
|
|
18075
18287
|
}
|
|
18076
18288
|
});
|
|
18077
18289
|
|
|
18078
|
-
// src/lib/output.ts
|
|
18079
|
-
var printTable = (rows) => {
|
|
18080
|
-
if (rows.length === 0)
|
|
18081
|
-
return;
|
|
18082
|
-
const columnCount = Math.max(...rows.map((row) => row.length));
|
|
18083
|
-
const widths = Array.from({ length: columnCount }, (_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length)));
|
|
18084
|
-
for (const row of rows) {
|
|
18085
|
-
console.log(row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" ").trimEnd());
|
|
18086
|
-
}
|
|
18087
|
-
};
|
|
18088
|
-
|
|
18089
18290
|
// src/commands/issues/list.ts
|
|
18090
18291
|
var list = command({
|
|
18091
18292
|
name: "list",
|
|
@@ -18155,6 +18356,24 @@ More issues match; raise --limit past ${options.limit}.`);
|
|
|
18155
18356
|
}
|
|
18156
18357
|
});
|
|
18157
18358
|
|
|
18359
|
+
// src/commands/issues/relate.ts
|
|
18360
|
+
var relate = command({
|
|
18361
|
+
name: "relate",
|
|
18362
|
+
description: "Relate two issues, stated from the first one's side (duplicate_of also cancels it)",
|
|
18363
|
+
positionals: {
|
|
18364
|
+
id: exports_external.string().describe("Issue identifier, number, or URL"),
|
|
18365
|
+
kind: exports_external.enum(ISSUE_RELATION_KINDS).describe(`How the first issue relates to the second (${ISSUE_RELATION_KINDS.join("|")})`),
|
|
18366
|
+
other: exports_external.string().describe("The other issue's identifier, number, or URL")
|
|
18367
|
+
},
|
|
18368
|
+
run: async ({ positionals: { id, kind, other } }) => {
|
|
18369
|
+
const client3 = await backendClient();
|
|
18370
|
+
const issue2 = await resolveIssue(client3, id);
|
|
18371
|
+
const otherIssue = await resolveIssue(client3, other);
|
|
18372
|
+
await client3.mutation(api2.issues.addRelation, { id: issue2._id, kind, otherIssueId: otherIssue._id });
|
|
18373
|
+
console.log(`${issue2.identifier} ${kind.replaceAll("_", " ")} ${otherIssue.identifier}.`);
|
|
18374
|
+
}
|
|
18375
|
+
});
|
|
18376
|
+
|
|
18158
18377
|
// src/commands/issues/remove.ts
|
|
18159
18378
|
var remove = command({
|
|
18160
18379
|
name: "delete",
|
|
@@ -18196,7 +18415,7 @@ var route = command({
|
|
|
18196
18415
|
// src/commands/issues/set.ts
|
|
18197
18416
|
var set2 = command({
|
|
18198
18417
|
name: "set",
|
|
18199
|
-
description: "Set an issue's status, priority, estimate, due date, project, milestone, or
|
|
18418
|
+
description: "Set an issue's status, priority, estimate, due date, project, milestone, parent, or labels",
|
|
18200
18419
|
positionals: {
|
|
18201
18420
|
id: exports_external.string().describe("Issue identifier, number, or URL")
|
|
18202
18421
|
},
|
|
@@ -18207,12 +18426,14 @@ var set2 = command({
|
|
|
18207
18426
|
due: exports_external.iso.date().nullable().optional().describe("Due date (YYYY-MM-DD), or --no-due to clear").meta({ negatable: true }),
|
|
18208
18427
|
project: exports_external.string().nullable().optional().describe("Move to this project (id or URL), or --no-project to make the issue projectless").meta({ negatable: true }),
|
|
18209
18428
|
milestone: exports_external.string().nullable().optional().describe("Move to this milestone of the issue's project (name), or --no-milestone to clear").meta({ negatable: true }),
|
|
18210
|
-
parent: exports_external.string().nullable().optional().describe("Make this a sub-issue of another issue, or --no-parent to promote it").meta({ negatable: true })
|
|
18429
|
+
parent: exports_external.string().nullable().optional().describe("Make this a sub-issue of another issue, or --no-parent to promote it").meta({ negatable: true }),
|
|
18430
|
+
label: exports_external.array(exports_external.string()).optional().describe("Add a label by name (repeatable)"),
|
|
18431
|
+
"no-label": exports_external.array(exports_external.string()).optional().describe("Remove a label by name (repeatable)")
|
|
18211
18432
|
},
|
|
18212
18433
|
run: async ({ positionals: { id }, options }) => {
|
|
18213
|
-
const { status: status2, priority, estimate, due, project, milestone, parent } = options;
|
|
18434
|
+
const { status: status2, priority, estimate, due, project, milestone, parent, label, "no-label": noLabel } = options;
|
|
18214
18435
|
if (Object.values(options).every((value) => value === undefined))
|
|
18215
|
-
throw new Error("Nothing to set. Pass --status, --priority, --estimate, --due, --project, --milestone, or --
|
|
18436
|
+
throw new Error("Nothing to set. Pass --status, --priority, --estimate, --due, --project, --milestone, --parent, or --label.");
|
|
18216
18437
|
const client3 = await backendClient();
|
|
18217
18438
|
const issue2 = await resolveIssue(client3, id);
|
|
18218
18439
|
const projectId = project === undefined ? issue2.projectId : project === null ? undefined : (await resolveProject(client3, project))._id;
|
|
@@ -18225,8 +18446,14 @@ var set2 = command({
|
|
|
18225
18446
|
milestoneId = (await resolveMilestone(client3, projectId, milestone))._id;
|
|
18226
18447
|
}
|
|
18227
18448
|
const parentId = parent == null ? undefined : (await resolveIssue(client3, parent))._id;
|
|
18449
|
+
const addLabels = await resolveIssueLabels(client3, label ?? []);
|
|
18450
|
+
const removeLabels = await resolveIssueLabels(client3, noLabel ?? []);
|
|
18228
18451
|
if (parent !== undefined)
|
|
18229
18452
|
await client3.mutation(api2.issues.setParent, { id: issue2._id, parentId });
|
|
18453
|
+
for (const { _id } of addLabels)
|
|
18454
|
+
await client3.mutation(api2.issues.addLabel, { issueId: issue2._id, labelId: _id });
|
|
18455
|
+
for (const { _id } of removeLabels)
|
|
18456
|
+
await client3.mutation(api2.issues.removeLabel, { issueId: issue2._id, labelId: _id });
|
|
18230
18457
|
if (status2 !== undefined)
|
|
18231
18458
|
await client3.mutation(api2.issues.setStatus, { id: issue2._id, status: status2 });
|
|
18232
18459
|
if (priority !== undefined)
|
|
@@ -18241,27 +18468,86 @@ var set2 = command({
|
|
|
18241
18468
|
}
|
|
18242
18469
|
});
|
|
18243
18470
|
|
|
18471
|
+
// src/commands/issues/start.ts
|
|
18472
|
+
var shellArg = (value) => /^[\w./-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
|
|
18473
|
+
var start = command({
|
|
18474
|
+
name: "start",
|
|
18475
|
+
description: "Claim an issue: move it to in_progress and record the branch to work under",
|
|
18476
|
+
positionals: {
|
|
18477
|
+
id: exports_external.string().describe("Issue identifier, number, or URL")
|
|
18478
|
+
},
|
|
18479
|
+
run: async ({ positionals: { id } }) => {
|
|
18480
|
+
const client3 = await backendClient();
|
|
18481
|
+
const issue2 = await resolveIssue(client3, id);
|
|
18482
|
+
const { branch } = await client3.mutation(api2.issues.start, { id: issue2._id });
|
|
18483
|
+
console.log(`Started ${issue2.identifier}: in_progress, branch ${branch}`);
|
|
18484
|
+
console.log(`
|
|
18485
|
+
git switch -c ${shellArg(branch)}`);
|
|
18486
|
+
}
|
|
18487
|
+
});
|
|
18488
|
+
|
|
18489
|
+
// src/commands/issues/unrelate.ts
|
|
18490
|
+
var relationEntries = (relations, kind) => {
|
|
18491
|
+
switch (kind) {
|
|
18492
|
+
case "blocks":
|
|
18493
|
+
return relations.blocks;
|
|
18494
|
+
case "blocked_by":
|
|
18495
|
+
return relations.blockedBy;
|
|
18496
|
+
case "duplicate_of":
|
|
18497
|
+
return relations.duplicateOf ? [relations.duplicateOf] : [];
|
|
18498
|
+
case "duplicated_by":
|
|
18499
|
+
return relations.duplicates;
|
|
18500
|
+
case "relates_to":
|
|
18501
|
+
return relations.relatesTo;
|
|
18502
|
+
}
|
|
18503
|
+
};
|
|
18504
|
+
var unrelate = command({
|
|
18505
|
+
name: "unrelate",
|
|
18506
|
+
description: "Remove a relation between two issues (a canceled duplicate stays canceled)",
|
|
18507
|
+
positionals: {
|
|
18508
|
+
id: exports_external.string().describe("Issue identifier, number, or URL"),
|
|
18509
|
+
kind: exports_external.enum(ISSUE_RELATION_KINDS).describe(`How the first issue relates to the second (${ISSUE_RELATION_KINDS.join("|")})`),
|
|
18510
|
+
other: exports_external.string().describe("The other issue's identifier, number, or URL")
|
|
18511
|
+
},
|
|
18512
|
+
run: async ({ positionals: { id, kind, other } }) => {
|
|
18513
|
+
const client3 = await backendClient();
|
|
18514
|
+
const issue2 = await resolveIssue(client3, id);
|
|
18515
|
+
const otherIssue = await resolveIssue(client3, other);
|
|
18516
|
+
const relations = await client3.query(api2.issues.relations, { id: issue2._id });
|
|
18517
|
+
const relation = relationEntries(relations, kind).find((entry) => entry.issue._id === otherIssue._id);
|
|
18518
|
+
const label = kind.replaceAll("_", " ");
|
|
18519
|
+
if (!relation)
|
|
18520
|
+
throw new Error(`${issue2.identifier} has no "${label}" relation to ${otherIssue.identifier}.`);
|
|
18521
|
+
await client3.mutation(api2.issues.removeRelation, { id: relation._id });
|
|
18522
|
+
console.log(`Removed: ${issue2.identifier} ${label} ${otherIssue.identifier}.`);
|
|
18523
|
+
}
|
|
18524
|
+
});
|
|
18525
|
+
|
|
18244
18526
|
// src/commands/issues/update.ts
|
|
18245
18527
|
var update = command({
|
|
18246
18528
|
name: "update",
|
|
18247
|
-
description: "Rewrite an issue's title or description",
|
|
18529
|
+
description: "Rewrite an issue's title or description, or attach files",
|
|
18248
18530
|
positionals: {
|
|
18249
18531
|
id: exports_external.string().describe("Issue identifier, number, or URL")
|
|
18250
18532
|
},
|
|
18251
18533
|
options: {
|
|
18252
18534
|
title: exports_external.string().optional().describe("New title").meta({ short: "t" }),
|
|
18253
|
-
description: exports_external.string().optional().describe("New markdown description, or - for stdin").meta({ short: "d" })
|
|
18535
|
+
description: exports_external.string().optional().describe("New markdown description, or - for stdin").meta({ short: "d" }),
|
|
18536
|
+
attach: exports_external.array(exports_external.string()).optional().describe("Attach a file to the issue (repeatable)").meta({ short: "a" })
|
|
18254
18537
|
},
|
|
18255
|
-
run: async ({ positionals: { id }, options: { title, description } }) => {
|
|
18256
|
-
if (title === undefined && description === undefined)
|
|
18257
|
-
throw new Error("Nothing to update. Pass --title or --
|
|
18538
|
+
run: async ({ positionals: { id }, options: { title, description, attach } }) => {
|
|
18539
|
+
if (title === undefined && description === undefined && attach === undefined)
|
|
18540
|
+
throw new Error("Nothing to update. Pass --title, --description, or --attach.");
|
|
18258
18541
|
const client3 = await backendClient();
|
|
18259
18542
|
const issue2 = await resolveIssue(client3, id);
|
|
18260
|
-
await client3.
|
|
18261
|
-
|
|
18262
|
-
|
|
18263
|
-
|
|
18264
|
-
|
|
18543
|
+
printAttachments(await attachFiles(client3, issue2._id, attach ?? []));
|
|
18544
|
+
if (title !== undefined || description !== undefined) {
|
|
18545
|
+
await client3.mutation(api2.issues.update, {
|
|
18546
|
+
id: issue2._id,
|
|
18547
|
+
title,
|
|
18548
|
+
descriptionMarkdown: description === undefined ? undefined : await readTextOption(description)
|
|
18549
|
+
});
|
|
18550
|
+
}
|
|
18265
18551
|
console.log(`Updated ${issue2.identifier}.`);
|
|
18266
18552
|
}
|
|
18267
18553
|
});
|
|
@@ -18270,11 +18556,11 @@ var update = command({
|
|
|
18270
18556
|
var issues = group({
|
|
18271
18557
|
name: "issues",
|
|
18272
18558
|
description: "Track and triage issues",
|
|
18273
|
-
commands: [create, list, get, update, set2, route, comment, remove]
|
|
18559
|
+
commands: [create, list, get, update, set2, route, start, relate, unrelate, comment, remove]
|
|
18274
18560
|
});
|
|
18275
18561
|
|
|
18276
18562
|
// src/lib/group.ts
|
|
18277
|
-
import { basename } from "path";
|
|
18563
|
+
import { basename as basename2 } from "path";
|
|
18278
18564
|
var git = async (...args) => {
|
|
18279
18565
|
const proc = Bun.spawn(["git", ...args], { stdout: "pipe", stderr: "ignore" });
|
|
18280
18566
|
const [output, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
@@ -18286,7 +18572,7 @@ var detectRepoGroup = async () => {
|
|
|
18286
18572
|
if (remote)
|
|
18287
18573
|
return repoNameFromRemote(remote);
|
|
18288
18574
|
const root = await git("rev-parse", "--show-toplevel");
|
|
18289
|
-
return root ?
|
|
18575
|
+
return root ? basename2(root) : undefined;
|
|
18290
18576
|
};
|
|
18291
18577
|
var groupForCreate = async (group2) => group2 === null ? undefined : group2 ?? await detectRepoGroup();
|
|
18292
18578
|
|
|
@@ -18694,7 +18980,7 @@ var upgrade = command({
|
|
|
18694
18980
|
var rootCommand = group({
|
|
18695
18981
|
name: "kds",
|
|
18696
18982
|
description: "KDS CLI",
|
|
18697
|
-
commands: [auth, issues, pages, project, projects, upgrade],
|
|
18983
|
+
commands: [auth, inbox, issues, pages, project, projects, upgrade],
|
|
18698
18984
|
options: {
|
|
18699
18985
|
version: exports_external.boolean().default(false).describe("Show the version").meta({ short: "v" })
|
|
18700
18986
|
},
|