@islamihab/kds 0.1.4 → 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/index.js +672 -77
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -14587,7 +14587,7 @@ import { join } from "path";
|
|
|
14587
14587
|
// package.json
|
|
14588
14588
|
var package_default = {
|
|
14589
14589
|
name: "cli",
|
|
14590
|
-
version: "0.
|
|
14590
|
+
version: "0.2.0",
|
|
14591
14591
|
private: true,
|
|
14592
14592
|
type: "module",
|
|
14593
14593
|
bin: {
|
|
@@ -15143,6 +15143,28 @@ var KDS_DEVICE_AUTH_CLIENT_ID = "kds-cli";
|
|
|
15143
15143
|
var PAGE_MODES = ["themed", "raw"];
|
|
15144
15144
|
var PAGE_VISIBILITIES = ["public", "private"];
|
|
15145
15145
|
var MAX_PAGE_HTML_BYTES = 4000000;
|
|
15146
|
+
var ISSUE_STATUSES = ["backlog", "todo", "in_progress", "done", "canceled"];
|
|
15147
|
+
var ISSUE_PRIORITIES = ["urgent", "high", "medium", "low", "none"];
|
|
15148
|
+
var ISSUE_DISPOSITIONS = [
|
|
15149
|
+
"needs_triage",
|
|
15150
|
+
"needs_info",
|
|
15151
|
+
"ready_for_agent",
|
|
15152
|
+
"ready_for_human",
|
|
15153
|
+
"wontfix"
|
|
15154
|
+
];
|
|
15155
|
+
var ISSUE_CREATE_DISPOSITIONS = [
|
|
15156
|
+
"needs_triage",
|
|
15157
|
+
"ready_for_agent",
|
|
15158
|
+
"ready_for_human"
|
|
15159
|
+
];
|
|
15160
|
+
var ISSUE_ESTIMATES = [1, 2, 3, 5, 8];
|
|
15161
|
+
var ISSUE_TERMINAL_STATUSES = ["done", "canceled"];
|
|
15162
|
+
var PROJECT_STATUSES = ["planned", "in_progress", "paused", "completed", "canceled"];
|
|
15163
|
+
var PROJECT_HEALTHS = ["on_track", "at_risk", "off_track"];
|
|
15164
|
+
var ISSUE_IDENTIFIER_PREFIX = "KAI";
|
|
15165
|
+
var ISSUE_LIST_PAGE_SIZE = 50;
|
|
15166
|
+
var ISSUE_DUE_DATE_MODES = ["overdue", "due_today", "due_soon", "no_due_date"];
|
|
15167
|
+
var issueIsOpen = (status) => !ISSUE_TERMINAL_STATUSES.some((terminal) => terminal === status);
|
|
15146
15168
|
var MAX_PROJECT_REPO_LENGTH = 200;
|
|
15147
15169
|
|
|
15148
15170
|
// src/commands/auth/login.ts
|
|
@@ -17103,6 +17125,12 @@ var normalizeUrl = (url2, errorMessage) => {
|
|
|
17103
17125
|
throw new Error(errorMessage ?? "URL must be a valid URL");
|
|
17104
17126
|
return new URL(parsed.data).origin;
|
|
17105
17127
|
};
|
|
17128
|
+
var localToday = () => {
|
|
17129
|
+
const now3 = new Date;
|
|
17130
|
+
const month = String(now3.getMonth() + 1).padStart(2, "0");
|
|
17131
|
+
const day = String(now3.getDate()).padStart(2, "0");
|
|
17132
|
+
return `${now3.getFullYear()}-${month}-${day}`;
|
|
17133
|
+
};
|
|
17106
17134
|
|
|
17107
17135
|
// src/lib/zod.ts
|
|
17108
17136
|
var configSchema = exports_external.object({ sessionToken: exports_external.string().optional(), convexUrl: exports_external.url(), convexSiteUrl: exports_external.url() });
|
|
@@ -17786,23 +17814,6 @@ var auth = group({
|
|
|
17786
17814
|
commands: [login, logout, status]
|
|
17787
17815
|
});
|
|
17788
17816
|
|
|
17789
|
-
// src/lib/group.ts
|
|
17790
|
-
import { basename } from "path";
|
|
17791
|
-
var git = async (...args) => {
|
|
17792
|
-
const proc = Bun.spawn(["git", ...args], { stdout: "pipe", stderr: "ignore" });
|
|
17793
|
-
const [output, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
17794
|
-
return exitCode === 0 ? output.trim() || null : null;
|
|
17795
|
-
};
|
|
17796
|
-
var repoNameFromRemote = (remote) => remote.replace(/\/+$/, "").replace(/\.git$/, "").split(/[/:]/).pop() || undefined;
|
|
17797
|
-
var detectRepoGroup = async () => {
|
|
17798
|
-
const remote = await git("remote", "get-url", "origin");
|
|
17799
|
-
if (remote)
|
|
17800
|
-
return repoNameFromRemote(remote);
|
|
17801
|
-
const root = await git("rev-parse", "--show-toplevel");
|
|
17802
|
-
return root ? basename(root) : undefined;
|
|
17803
|
-
};
|
|
17804
|
-
var groupForCreate = async (group2) => group2 === null ? undefined : group2 ?? await detectRepoGroup();
|
|
17805
|
-
|
|
17806
17817
|
// src/lib/input.ts
|
|
17807
17818
|
var assertSize = (size) => {
|
|
17808
17819
|
if (size > MAX_PAGE_HTML_BYTES)
|
|
@@ -17818,6 +17829,27 @@ var readBody = async (path) => {
|
|
|
17818
17829
|
assertSize(file2.size);
|
|
17819
17830
|
return await file2.text();
|
|
17820
17831
|
};
|
|
17832
|
+
var readTextOption = async (value) => value === "-" ? await Bun.stdin.text() : value;
|
|
17833
|
+
var readIssueRef = (value) => {
|
|
17834
|
+
const trimmed = value.trim();
|
|
17835
|
+
if (/^\d+$/.test(trimmed))
|
|
17836
|
+
return `${ISSUE_IDENTIFIER_PREFIX}-${trimmed}`;
|
|
17837
|
+
try {
|
|
17838
|
+
const url2 = new URL(trimmed);
|
|
17839
|
+
return url2.pathname.match(/\/issues\/([^/]+)\/?$/)?.[1] ?? trimmed;
|
|
17840
|
+
} catch {
|
|
17841
|
+
return trimmed;
|
|
17842
|
+
}
|
|
17843
|
+
};
|
|
17844
|
+
var readProjectRef = (value) => {
|
|
17845
|
+
const trimmed = value.trim();
|
|
17846
|
+
try {
|
|
17847
|
+
const url2 = new URL(trimmed);
|
|
17848
|
+
return url2.pathname.match(/\/projects\/([^/]+)\/?$/)?.[1] ?? trimmed;
|
|
17849
|
+
} catch {
|
|
17850
|
+
return trimmed;
|
|
17851
|
+
}
|
|
17852
|
+
};
|
|
17821
17853
|
var readPageId = (value) => {
|
|
17822
17854
|
const trimmed = value.trim();
|
|
17823
17855
|
try {
|
|
@@ -17829,8 +17861,437 @@ var readPageId = (value) => {
|
|
|
17829
17861
|
}
|
|
17830
17862
|
};
|
|
17831
17863
|
|
|
17832
|
-
//
|
|
17864
|
+
// ../../packages/backend/convex/lib/projectRepo.ts
|
|
17865
|
+
var DEFAULT_REPO_HOST = "github.com";
|
|
17866
|
+
var REPO_FORMAT_ERROR = "Enter a repository like github.com/owner/name.";
|
|
17867
|
+
var HOST_PATTERN = /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/;
|
|
17868
|
+
var SEGMENT_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
17869
|
+
var normalizeProjectRepo = (value) => {
|
|
17870
|
+
const trimmed = value?.trim();
|
|
17871
|
+
if (!trimmed)
|
|
17872
|
+
return;
|
|
17873
|
+
if (trimmed.length > MAX_PROJECT_REPO_LENGTH) {
|
|
17874
|
+
throw new ConvexError(`Repository must be ${MAX_PROJECT_REPO_LENGTH} characters or fewer.`);
|
|
17875
|
+
}
|
|
17876
|
+
const path = trimmed.toLowerCase().replace(/^(https?|ssh|git):\/\//, "").replace(/^[^/@:]+@/, "").replace(":", "/").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
17877
|
+
const segments = path.split("/");
|
|
17878
|
+
const [host, owner, name] = segments.length === 2 ? [DEFAULT_REPO_HOST, ...segments] : segments.length === 3 ? segments : [];
|
|
17879
|
+
if (!host || !owner || !name)
|
|
17880
|
+
throw new ConvexError(REPO_FORMAT_ERROR);
|
|
17881
|
+
if (!HOST_PATTERN.test(host) || !SEGMENT_PATTERN.test(owner) || !SEGMENT_PATTERN.test(name)) {
|
|
17882
|
+
throw new ConvexError(REPO_FORMAT_ERROR);
|
|
17883
|
+
}
|
|
17884
|
+
const key = `${host}/${owner}/${name}`;
|
|
17885
|
+
if (key.length > MAX_PROJECT_REPO_LENGTH) {
|
|
17886
|
+
throw new ConvexError(`Repository must be ${MAX_PROJECT_REPO_LENGTH} characters or fewer.`);
|
|
17887
|
+
}
|
|
17888
|
+
return key;
|
|
17889
|
+
};
|
|
17890
|
+
var tryNormalizeProjectRepo = (value) => {
|
|
17891
|
+
try {
|
|
17892
|
+
return normalizeProjectRepo(value);
|
|
17893
|
+
} catch {
|
|
17894
|
+
return;
|
|
17895
|
+
}
|
|
17896
|
+
};
|
|
17897
|
+
|
|
17898
|
+
// src/lib/repo.ts
|
|
17899
|
+
var currentRepoKey = async () => {
|
|
17900
|
+
const proc = Bun.spawn(["git", "remote", "get-url", "origin"], { stdout: "pipe", stderr: "ignore" });
|
|
17901
|
+
const url2 = await new Response(proc.stdout).text();
|
|
17902
|
+
return await proc.exited === 0 ? tryNormalizeProjectRepo(url2) : undefined;
|
|
17903
|
+
};
|
|
17904
|
+
|
|
17905
|
+
// src/lib/resolve.ts
|
|
17906
|
+
var resolveIssue = async (client3, ref) => {
|
|
17907
|
+
const issue2 = await client3.query(api2.issues.get, { identifier: readIssueRef(ref) });
|
|
17908
|
+
if (!issue2)
|
|
17909
|
+
throw new Error(`No issue matches ${ref}.`);
|
|
17910
|
+
return issue2;
|
|
17911
|
+
};
|
|
17912
|
+
var resolveProject = async (client3, ref) => {
|
|
17913
|
+
const project = await client3.query(api2.projects.get, { id: readProjectRef(ref), today: localToday() });
|
|
17914
|
+
if (!project)
|
|
17915
|
+
throw new Error(`No project matches ${ref}.`);
|
|
17916
|
+
return project;
|
|
17917
|
+
};
|
|
17918
|
+
var repoProject = async (client3) => {
|
|
17919
|
+
const repo = await currentRepoKey();
|
|
17920
|
+
if (!repo)
|
|
17921
|
+
throw new Error("No repository here: not a git checkout with an origin remote.");
|
|
17922
|
+
const project = await client3.query(api2.projects.findByRepo, { repo });
|
|
17923
|
+
if (!project)
|
|
17924
|
+
throw new Error(`No project is connected to ${repo}. Connect one on the project in the dashboard.`);
|
|
17925
|
+
return project;
|
|
17926
|
+
};
|
|
17927
|
+
var resolveMilestone = async (client3, projectId, name) => {
|
|
17928
|
+
const milestones = await client3.query(api2.milestones.listByProject, { projectId, today: localToday() });
|
|
17929
|
+
const wanted = name.trim().toLowerCase();
|
|
17930
|
+
const milestone = milestones.find((candidate) => candidate.name.toLowerCase() === wanted);
|
|
17931
|
+
if (!milestone) {
|
|
17932
|
+
const names = milestones.map((candidate) => candidate.name).join(", ");
|
|
17933
|
+
throw new Error(names ? `No milestone named "${name}". The project has: ${names}.` : "The project has no milestones.");
|
|
17934
|
+
}
|
|
17935
|
+
return milestone;
|
|
17936
|
+
};
|
|
17937
|
+
|
|
17938
|
+
// src/commands/issues/comment.ts
|
|
17939
|
+
var comment = command({
|
|
17940
|
+
name: "comment",
|
|
17941
|
+
description: "Comment on an issue",
|
|
17942
|
+
positionals: {
|
|
17943
|
+
id: exports_external.string().describe("Issue identifier, number, or URL"),
|
|
17944
|
+
body: exports_external.string().describe("Comment markdown, or - for stdin")
|
|
17945
|
+
},
|
|
17946
|
+
run: async ({ positionals: { id, body } }) => {
|
|
17947
|
+
const client3 = await backendClient();
|
|
17948
|
+
const issue2 = await resolveIssue(client3, id);
|
|
17949
|
+
await client3.mutation(api2.issueComments.create, { issueId: issue2._id, bodyMarkdown: await readTextOption(body) });
|
|
17950
|
+
console.log(`Commented on ${issue2.identifier}.`);
|
|
17951
|
+
}
|
|
17952
|
+
});
|
|
17953
|
+
|
|
17954
|
+
// src/commands/issues/create.ts
|
|
17833
17955
|
var create = command({
|
|
17956
|
+
name: "create",
|
|
17957
|
+
description: "Create an issue and print its identifier",
|
|
17958
|
+
positionals: {
|
|
17959
|
+
title: exports_external.string().describe("Issue title")
|
|
17960
|
+
},
|
|
17961
|
+
options: {
|
|
17962
|
+
description: exports_external.string().optional().describe("Markdown description, or - for stdin").meta({ short: "d" }),
|
|
17963
|
+
status: exports_external.enum(ISSUE_STATUSES).optional().describe("Starting status (default backlog)").meta({ short: "s" }),
|
|
17964
|
+
priority: exports_external.enum(ISSUE_PRIORITIES).optional().describe("Priority (default none)").meta({ short: "p" }),
|
|
17965
|
+
estimate: exports_external.coerce.number().pipe(exports_external.literal([...ISSUE_ESTIMATES])).optional().describe(`Points (${ISSUE_ESTIMATES.join("|")})`),
|
|
17966
|
+
due: exports_external.iso.date().optional().describe("Due date (YYYY-MM-DD)"),
|
|
17967
|
+
project: exports_external.string().optional().describe("Project to create the issue in (id or URL)"),
|
|
17968
|
+
here: exports_external.boolean().default(false).describe("Create in the checkout's connected project"),
|
|
17969
|
+
milestone: exports_external.string().optional().describe("Milestone name (needs --project or --here)"),
|
|
17970
|
+
disposition: exports_external.enum(ISSUE_CREATE_DISPOSITIONS).optional().describe("Route immediately (default needs_triage)")
|
|
17971
|
+
},
|
|
17972
|
+
run: async ({ positionals: { title }, options }) => {
|
|
17973
|
+
if (options.project && options.here)
|
|
17974
|
+
throw new Error("Pass --project or --here, not both.");
|
|
17975
|
+
const client3 = await backendClient();
|
|
17976
|
+
const project = options.project ? await resolveProject(client3, options.project) : options.here ? await repoProject(client3) : undefined;
|
|
17977
|
+
if (options.milestone && !project)
|
|
17978
|
+
throw new Error("A milestone needs --project or --here.");
|
|
17979
|
+
const milestone = project && options.milestone ? await resolveMilestone(client3, project._id, options.milestone) : undefined;
|
|
17980
|
+
const { identifier } = await client3.mutation(api2.issues.create, {
|
|
17981
|
+
title,
|
|
17982
|
+
descriptionMarkdown: options.description === undefined ? undefined : await readTextOption(options.description),
|
|
17983
|
+
status: options.status,
|
|
17984
|
+
priority: options.priority,
|
|
17985
|
+
estimate: options.estimate,
|
|
17986
|
+
dueDate: options.due,
|
|
17987
|
+
projectId: project?._id,
|
|
17988
|
+
milestoneId: milestone?._id,
|
|
17989
|
+
disposition: options.disposition
|
|
17990
|
+
});
|
|
17991
|
+
console.log(identifier);
|
|
17992
|
+
}
|
|
17993
|
+
});
|
|
17994
|
+
|
|
17995
|
+
// 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
|
+
var get = command({
|
|
18026
|
+
name: "get",
|
|
18027
|
+
description: "Show an issue: properties, description, and its feed",
|
|
18028
|
+
positionals: {
|
|
18029
|
+
id: exports_external.string().describe("Issue identifier, number, or URL")
|
|
18030
|
+
},
|
|
18031
|
+
options: {
|
|
18032
|
+
json: exports_external.boolean().default(false).describe("Print as JSON")
|
|
18033
|
+
},
|
|
18034
|
+
run: async ({ positionals: { id }, options: { json: json2 } }) => {
|
|
18035
|
+
const client3 = await backendClient();
|
|
18036
|
+
const issue2 = await resolveIssue(client3, id);
|
|
18037
|
+
const feed = await client3.query(api2.issues.feed, { id: issue2._id });
|
|
18038
|
+
if (json2)
|
|
18039
|
+
return console.log(JSON.stringify({ ...issue2, feed }, null, 2));
|
|
18040
|
+
const project = issue2.projectId ? await client3.query(api2.projects.get, { id: issue2.projectId, today: localToday() }) : null;
|
|
18041
|
+
const milestone = issue2.milestoneId ? await client3.query(api2.milestones.get, { id: issue2.milestoneId, today: localToday() }) : null;
|
|
18042
|
+
console.log(`${issue2.identifier} ${issue2.title}`);
|
|
18043
|
+
console.log(`Status: ${issue2.status} \xB7 Priority: ${issue2.priority} \xB7 Disposition: ${issue2.disposition}`);
|
|
18044
|
+
if (issue2.estimate !== undefined)
|
|
18045
|
+
console.log(`Estimate: ${issue2.estimate}`);
|
|
18046
|
+
if (issue2.dueDate)
|
|
18047
|
+
console.log(`Due: ${issue2.dueDate}`);
|
|
18048
|
+
if (project)
|
|
18049
|
+
console.log(`Project: ${project.name}${milestone ? ` \xB7 Milestone: ${milestone.name}` : ""}`);
|
|
18050
|
+
if (issue2.parent)
|
|
18051
|
+
console.log(`Parent: ${issue2.parent.identifier} ${issue2.parent.title}`);
|
|
18052
|
+
if (issue2.labels.length > 0)
|
|
18053
|
+
console.log(`Labels: ${issue2.labels.map((label) => label.name).join(", ")}`);
|
|
18054
|
+
if (issue2.children.total > 0)
|
|
18055
|
+
console.log(`Sub-issues: ${issue2.children.done}/${issue2.children.total} done`);
|
|
18056
|
+
if (issue2.descriptionMarkdown)
|
|
18057
|
+
console.log(`
|
|
18058
|
+
${issue2.descriptionMarkdown}`);
|
|
18059
|
+
if (feed.events.length > 0)
|
|
18060
|
+
console.log(`
|
|
18061
|
+
Feed:`);
|
|
18062
|
+
if (feed.truncated)
|
|
18063
|
+
console.log("(older events truncated)");
|
|
18064
|
+
for (const event of feed.events) {
|
|
18065
|
+
const at = new Date(event.at).toISOString().slice(0, 16).replace("T", " ");
|
|
18066
|
+
if (event.type === "activity") {
|
|
18067
|
+
console.log(`${at} ${activityLine(event.detail)}`);
|
|
18068
|
+
} else {
|
|
18069
|
+
console.log(`${at} comment${event.editedAt ? " (edited)" : ""}:`);
|
|
18070
|
+
for (const line of event.bodyMarkdown.split(`
|
|
18071
|
+
`))
|
|
18072
|
+
console.log(` ${line}`);
|
|
18073
|
+
}
|
|
18074
|
+
}
|
|
18075
|
+
}
|
|
18076
|
+
});
|
|
18077
|
+
|
|
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
|
+
// src/commands/issues/list.ts
|
|
18090
|
+
var list = command({
|
|
18091
|
+
name: "list",
|
|
18092
|
+
description: "List open issues, most recently updated first",
|
|
18093
|
+
options: {
|
|
18094
|
+
all: exports_external.boolean().default(false).describe("Include done and canceled issues"),
|
|
18095
|
+
status: exports_external.enum(ISSUE_STATUSES).optional().describe("Only this status").meta({ short: "s" }),
|
|
18096
|
+
priority: exports_external.enum(ISSUE_PRIORITIES).optional().describe("Only this priority").meta({ short: "p" }),
|
|
18097
|
+
disposition: exports_external.enum(ISSUE_DISPOSITIONS).optional().describe("Only this disposition").meta({ short: "d" }),
|
|
18098
|
+
project: exports_external.string().optional().describe("Only this project (id or URL)"),
|
|
18099
|
+
here: exports_external.boolean().default(false).describe("Only the checkout's connected project"),
|
|
18100
|
+
milestone: exports_external.string().optional().describe("Only this milestone (name; needs --project or --here)"),
|
|
18101
|
+
due: exports_external.enum(ISSUE_DUE_DATE_MODES).optional().describe("Only this due-date state"),
|
|
18102
|
+
search: exports_external.string().optional().describe("Free-text search (results come in relevance order)"),
|
|
18103
|
+
limit: exports_external.coerce.number().int().positive().default(ISSUE_LIST_PAGE_SIZE).describe("Most issues to print"),
|
|
18104
|
+
json: exports_external.boolean().default(false).describe("Print as JSON")
|
|
18105
|
+
},
|
|
18106
|
+
run: async ({ options }) => {
|
|
18107
|
+
if (options.project && options.here)
|
|
18108
|
+
throw new Error("Pass --project or --here, not both.");
|
|
18109
|
+
const client3 = await backendClient();
|
|
18110
|
+
const project = options.project ? await resolveProject(client3, options.project) : options.here ? await repoProject(client3) : undefined;
|
|
18111
|
+
if (options.milestone && !project)
|
|
18112
|
+
throw new Error("A milestone filter needs --project or --here.");
|
|
18113
|
+
const milestone = project && options.milestone ? await resolveMilestone(client3, project._id, options.milestone) : undefined;
|
|
18114
|
+
const result = await client3.query(api2.issueViews.query, {
|
|
18115
|
+
source: {
|
|
18116
|
+
type: "custom",
|
|
18117
|
+
query: {
|
|
18118
|
+
layout: "list",
|
|
18119
|
+
groupBy: "none",
|
|
18120
|
+
orderBy: "updated_at",
|
|
18121
|
+
orderDirection: "desc",
|
|
18122
|
+
filters: {
|
|
18123
|
+
query: options.search,
|
|
18124
|
+
statuses: options.status ? [options.status] : options.all ? undefined : ISSUE_STATUSES.filter(issueIsOpen),
|
|
18125
|
+
priorities: options.priority ? [options.priority] : undefined,
|
|
18126
|
+
dispositions: options.disposition ? [options.disposition] : undefined,
|
|
18127
|
+
projectIds: project ? [project._id] : undefined,
|
|
18128
|
+
milestoneIds: milestone ? [milestone._id] : undefined,
|
|
18129
|
+
dueDate: options.due
|
|
18130
|
+
}
|
|
18131
|
+
}
|
|
18132
|
+
},
|
|
18133
|
+
paginationOpts: { numItems: options.limit, cursor: null },
|
|
18134
|
+
today: localToday()
|
|
18135
|
+
});
|
|
18136
|
+
if (options.json)
|
|
18137
|
+
return console.log(JSON.stringify(result.page, null, 2));
|
|
18138
|
+
if (result.page.length === 0)
|
|
18139
|
+
return console.log("No issues match.");
|
|
18140
|
+
printTable([
|
|
18141
|
+
["ID", "TITLE", "STATUS", "PRIORITY", "DISPOSITION", "DUE", "UPDATED"],
|
|
18142
|
+
...result.page.map((issue2) => [
|
|
18143
|
+
issue2.identifier,
|
|
18144
|
+
issue2.title,
|
|
18145
|
+
issue2.status,
|
|
18146
|
+
issue2.priority,
|
|
18147
|
+
issue2.disposition,
|
|
18148
|
+
issue2.dueDate ?? "-",
|
|
18149
|
+
new Date(issue2.updatedAt).toISOString().slice(0, 10)
|
|
18150
|
+
])
|
|
18151
|
+
]);
|
|
18152
|
+
if (!result.isDone)
|
|
18153
|
+
console.log(`
|
|
18154
|
+
More issues match; raise --limit past ${options.limit}.`);
|
|
18155
|
+
}
|
|
18156
|
+
});
|
|
18157
|
+
|
|
18158
|
+
// src/commands/issues/remove.ts
|
|
18159
|
+
var remove = command({
|
|
18160
|
+
name: "delete",
|
|
18161
|
+
description: "Delete an issue permanently (sub-issues survive as top-level issues)",
|
|
18162
|
+
positionals: {
|
|
18163
|
+
id: exports_external.string().describe("Issue identifier, number, or URL")
|
|
18164
|
+
},
|
|
18165
|
+
run: async ({ positionals: { id } }) => {
|
|
18166
|
+
const client3 = await backendClient();
|
|
18167
|
+
const issue2 = await resolveIssue(client3, id);
|
|
18168
|
+
await client3.mutation(api2.issues.remove, { id: issue2._id });
|
|
18169
|
+
console.log(`Deleted ${issue2.identifier}.`);
|
|
18170
|
+
}
|
|
18171
|
+
});
|
|
18172
|
+
|
|
18173
|
+
// src/commands/issues/route.ts
|
|
18174
|
+
var route = command({
|
|
18175
|
+
name: "route",
|
|
18176
|
+
description: "Route an issue to a disposition (wontfix also cancels it)",
|
|
18177
|
+
positionals: {
|
|
18178
|
+
id: exports_external.string().describe("Issue identifier, number, or URL"),
|
|
18179
|
+
disposition: exports_external.enum(ISSUE_DISPOSITIONS).describe("Where the issue goes next")
|
|
18180
|
+
},
|
|
18181
|
+
options: {
|
|
18182
|
+
comment: exports_external.string().optional().describe("Why, as a comment (required for needs_info and wontfix), or - for stdin").meta({ short: "c" })
|
|
18183
|
+
},
|
|
18184
|
+
run: async ({ positionals: { id, disposition }, options: { comment: comment2 } }) => {
|
|
18185
|
+
const client3 = await backendClient();
|
|
18186
|
+
const issue2 = await resolveIssue(client3, id);
|
|
18187
|
+
await client3.mutation(api2.issues.route, {
|
|
18188
|
+
id: issue2._id,
|
|
18189
|
+
disposition,
|
|
18190
|
+
comment: comment2 === undefined ? undefined : await readTextOption(comment2)
|
|
18191
|
+
});
|
|
18192
|
+
console.log(`Routed ${issue2.identifier} to ${disposition}.`);
|
|
18193
|
+
}
|
|
18194
|
+
});
|
|
18195
|
+
|
|
18196
|
+
// src/commands/issues/set.ts
|
|
18197
|
+
var set2 = command({
|
|
18198
|
+
name: "set",
|
|
18199
|
+
description: "Set an issue's status, priority, estimate, due date, project, milestone, or parent",
|
|
18200
|
+
positionals: {
|
|
18201
|
+
id: exports_external.string().describe("Issue identifier, number, or URL")
|
|
18202
|
+
},
|
|
18203
|
+
options: {
|
|
18204
|
+
status: exports_external.enum(ISSUE_STATUSES).optional().describe("New status").meta({ short: "s" }),
|
|
18205
|
+
priority: exports_external.enum(ISSUE_PRIORITIES).optional().describe("New priority").meta({ short: "p" }),
|
|
18206
|
+
estimate: exports_external.coerce.number().pipe(exports_external.literal([...ISSUE_ESTIMATES])).nullable().optional().describe(`Points (${ISSUE_ESTIMATES.join("|")}), or --no-estimate to clear`).meta({ negatable: true }),
|
|
18207
|
+
due: exports_external.iso.date().nullable().optional().describe("Due date (YYYY-MM-DD), or --no-due to clear").meta({ negatable: true }),
|
|
18208
|
+
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
|
+
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 })
|
|
18211
|
+
},
|
|
18212
|
+
run: async ({ positionals: { id }, options }) => {
|
|
18213
|
+
const { status: status2, priority, estimate, due, project, milestone, parent } = options;
|
|
18214
|
+
if (Object.values(options).every((value) => value === undefined))
|
|
18215
|
+
throw new Error("Nothing to set. Pass --status, --priority, --estimate, --due, --project, --milestone, or --parent.");
|
|
18216
|
+
const client3 = await backendClient();
|
|
18217
|
+
const issue2 = await resolveIssue(client3, id);
|
|
18218
|
+
const projectId = project === undefined ? issue2.projectId : project === null ? undefined : (await resolveProject(client3, project))._id;
|
|
18219
|
+
let milestoneId;
|
|
18220
|
+
if (milestone === undefined) {
|
|
18221
|
+
milestoneId = projectId === issue2.projectId ? issue2.milestoneId : undefined;
|
|
18222
|
+
} else if (milestone !== null) {
|
|
18223
|
+
if (projectId === undefined)
|
|
18224
|
+
throw new Error("A milestone needs a project. Pass --project too.");
|
|
18225
|
+
milestoneId = (await resolveMilestone(client3, projectId, milestone))._id;
|
|
18226
|
+
}
|
|
18227
|
+
const parentId = parent == null ? undefined : (await resolveIssue(client3, parent))._id;
|
|
18228
|
+
if (parent !== undefined)
|
|
18229
|
+
await client3.mutation(api2.issues.setParent, { id: issue2._id, parentId });
|
|
18230
|
+
if (status2 !== undefined)
|
|
18231
|
+
await client3.mutation(api2.issues.setStatus, { id: issue2._id, status: status2 });
|
|
18232
|
+
if (priority !== undefined)
|
|
18233
|
+
await client3.mutation(api2.issues.setPriority, { id: issue2._id, priority });
|
|
18234
|
+
if (estimate !== undefined)
|
|
18235
|
+
await client3.mutation(api2.issues.setEstimate, { id: issue2._id, estimate: estimate ?? undefined });
|
|
18236
|
+
if (due !== undefined)
|
|
18237
|
+
await client3.mutation(api2.issues.setDueDate, { id: issue2._id, dueDate: due ?? undefined });
|
|
18238
|
+
if (project !== undefined || milestone !== undefined)
|
|
18239
|
+
await client3.mutation(api2.issues.setMembership, { id: issue2._id, projectId, milestoneId });
|
|
18240
|
+
console.log(`Updated ${issue2.identifier}.`);
|
|
18241
|
+
}
|
|
18242
|
+
});
|
|
18243
|
+
|
|
18244
|
+
// src/commands/issues/update.ts
|
|
18245
|
+
var update = command({
|
|
18246
|
+
name: "update",
|
|
18247
|
+
description: "Rewrite an issue's title or description",
|
|
18248
|
+
positionals: {
|
|
18249
|
+
id: exports_external.string().describe("Issue identifier, number, or URL")
|
|
18250
|
+
},
|
|
18251
|
+
options: {
|
|
18252
|
+
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" })
|
|
18254
|
+
},
|
|
18255
|
+
run: async ({ positionals: { id }, options: { title, description } }) => {
|
|
18256
|
+
if (title === undefined && description === undefined)
|
|
18257
|
+
throw new Error("Nothing to update. Pass --title or --description.");
|
|
18258
|
+
const client3 = await backendClient();
|
|
18259
|
+
const issue2 = await resolveIssue(client3, id);
|
|
18260
|
+
await client3.mutation(api2.issues.update, {
|
|
18261
|
+
id: issue2._id,
|
|
18262
|
+
title,
|
|
18263
|
+
descriptionMarkdown: description === undefined ? undefined : await readTextOption(description)
|
|
18264
|
+
});
|
|
18265
|
+
console.log(`Updated ${issue2.identifier}.`);
|
|
18266
|
+
}
|
|
18267
|
+
});
|
|
18268
|
+
|
|
18269
|
+
// src/commands/issues/index.ts
|
|
18270
|
+
var issues = group({
|
|
18271
|
+
name: "issues",
|
|
18272
|
+
description: "Track and triage issues",
|
|
18273
|
+
commands: [create, list, get, update, set2, route, comment, remove]
|
|
18274
|
+
});
|
|
18275
|
+
|
|
18276
|
+
// src/lib/group.ts
|
|
18277
|
+
import { basename } from "path";
|
|
18278
|
+
var git = async (...args) => {
|
|
18279
|
+
const proc = Bun.spawn(["git", ...args], { stdout: "pipe", stderr: "ignore" });
|
|
18280
|
+
const [output, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
18281
|
+
return exitCode === 0 ? output.trim() || null : null;
|
|
18282
|
+
};
|
|
18283
|
+
var repoNameFromRemote = (remote) => remote.replace(/\/+$/, "").replace(/\.git$/, "").split(/[/:]/).pop() || undefined;
|
|
18284
|
+
var detectRepoGroup = async () => {
|
|
18285
|
+
const remote = await git("remote", "get-url", "origin");
|
|
18286
|
+
if (remote)
|
|
18287
|
+
return repoNameFromRemote(remote);
|
|
18288
|
+
const root = await git("rev-parse", "--show-toplevel");
|
|
18289
|
+
return root ? basename(root) : undefined;
|
|
18290
|
+
};
|
|
18291
|
+
var groupForCreate = async (group2) => group2 === null ? undefined : group2 ?? await detectRepoGroup();
|
|
18292
|
+
|
|
18293
|
+
// src/commands/pages/create.ts
|
|
18294
|
+
var create2 = command({
|
|
17834
18295
|
name: "create",
|
|
17835
18296
|
description: "Publish a page and print its URL",
|
|
17836
18297
|
positionals: {
|
|
@@ -17859,7 +18320,7 @@ var create = command({
|
|
|
17859
18320
|
});
|
|
17860
18321
|
|
|
17861
18322
|
// src/commands/pages/get.ts
|
|
17862
|
-
var
|
|
18323
|
+
var get2 = command({
|
|
17863
18324
|
name: "get",
|
|
17864
18325
|
description: "Print a page's HTML",
|
|
17865
18326
|
positionals: {
|
|
@@ -17881,19 +18342,8 @@ var get = command({
|
|
|
17881
18342
|
// ../../packages/backend/convex/lib/format.ts
|
|
17882
18343
|
var formatBytes = (bytes) => bytes < 1024 ? `${bytes} B` : `${Math.round(bytes / 1024)} KB`;
|
|
17883
18344
|
|
|
17884
|
-
// src/lib/output.ts
|
|
17885
|
-
var printTable = (rows) => {
|
|
17886
|
-
if (rows.length === 0)
|
|
17887
|
-
return;
|
|
17888
|
-
const columnCount = Math.max(...rows.map((row) => row.length));
|
|
17889
|
-
const widths = Array.from({ length: columnCount }, (_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length)));
|
|
17890
|
-
for (const row of rows) {
|
|
17891
|
-
console.log(row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" ").trimEnd());
|
|
17892
|
-
}
|
|
17893
|
-
};
|
|
17894
|
-
|
|
17895
18345
|
// src/commands/pages/list.ts
|
|
17896
|
-
var
|
|
18346
|
+
var list2 = command({
|
|
17897
18347
|
name: "list",
|
|
17898
18348
|
description: "List your published pages",
|
|
17899
18349
|
options: {
|
|
@@ -17923,7 +18373,7 @@ var list = command({
|
|
|
17923
18373
|
});
|
|
17924
18374
|
|
|
17925
18375
|
// src/commands/pages/remove.ts
|
|
17926
|
-
var
|
|
18376
|
+
var remove2 = command({
|
|
17927
18377
|
name: "delete",
|
|
17928
18378
|
description: "Delete a page",
|
|
17929
18379
|
positionals: {
|
|
@@ -17950,7 +18400,7 @@ var revert = command({
|
|
|
17950
18400
|
});
|
|
17951
18401
|
|
|
17952
18402
|
// src/commands/pages/update.ts
|
|
17953
|
-
var
|
|
18403
|
+
var update2 = command({
|
|
17954
18404
|
name: "update",
|
|
17955
18405
|
description: "Replace a page's HTML, title, group, mode, or visibility",
|
|
17956
18406
|
positionals: {
|
|
@@ -18011,50 +18461,9 @@ var versions2 = command({
|
|
|
18011
18461
|
var pages = group({
|
|
18012
18462
|
name: "pages",
|
|
18013
18463
|
description: "Publish HTML documents to the web",
|
|
18014
|
-
commands: [
|
|
18464
|
+
commands: [create2, list2, get2, update2, versions2, revert, remove2]
|
|
18015
18465
|
});
|
|
18016
18466
|
|
|
18017
|
-
// ../../packages/backend/convex/lib/projectRepo.ts
|
|
18018
|
-
var DEFAULT_REPO_HOST = "github.com";
|
|
18019
|
-
var REPO_FORMAT_ERROR = "Enter a repository like github.com/owner/name.";
|
|
18020
|
-
var HOST_PATTERN = /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/;
|
|
18021
|
-
var SEGMENT_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
18022
|
-
var normalizeProjectRepo = (value) => {
|
|
18023
|
-
const trimmed = value?.trim();
|
|
18024
|
-
if (!trimmed)
|
|
18025
|
-
return;
|
|
18026
|
-
if (trimmed.length > MAX_PROJECT_REPO_LENGTH) {
|
|
18027
|
-
throw new ConvexError(`Repository must be ${MAX_PROJECT_REPO_LENGTH} characters or fewer.`);
|
|
18028
|
-
}
|
|
18029
|
-
const path = trimmed.toLowerCase().replace(/^(https?|ssh|git):\/\//, "").replace(/^[^/@:]+@/, "").replace(":", "/").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18030
|
-
const segments = path.split("/");
|
|
18031
|
-
const [host, owner, name] = segments.length === 2 ? [DEFAULT_REPO_HOST, ...segments] : segments.length === 3 ? segments : [];
|
|
18032
|
-
if (!host || !owner || !name)
|
|
18033
|
-
throw new ConvexError(REPO_FORMAT_ERROR);
|
|
18034
|
-
if (!HOST_PATTERN.test(host) || !SEGMENT_PATTERN.test(owner) || !SEGMENT_PATTERN.test(name)) {
|
|
18035
|
-
throw new ConvexError(REPO_FORMAT_ERROR);
|
|
18036
|
-
}
|
|
18037
|
-
const key = `${host}/${owner}/${name}`;
|
|
18038
|
-
if (key.length > MAX_PROJECT_REPO_LENGTH) {
|
|
18039
|
-
throw new ConvexError(`Repository must be ${MAX_PROJECT_REPO_LENGTH} characters or fewer.`);
|
|
18040
|
-
}
|
|
18041
|
-
return key;
|
|
18042
|
-
};
|
|
18043
|
-
var tryNormalizeProjectRepo = (value) => {
|
|
18044
|
-
try {
|
|
18045
|
-
return normalizeProjectRepo(value);
|
|
18046
|
-
} catch {
|
|
18047
|
-
return;
|
|
18048
|
-
}
|
|
18049
|
-
};
|
|
18050
|
-
|
|
18051
|
-
// src/lib/repo.ts
|
|
18052
|
-
var currentRepoKey = async () => {
|
|
18053
|
-
const proc = Bun.spawn(["git", "remote", "get-url", "origin"], { stdout: "pipe", stderr: "ignore" });
|
|
18054
|
-
const url2 = await new Response(proc.stdout).text();
|
|
18055
|
-
return await proc.exited === 0 ? tryNormalizeProjectRepo(url2) : undefined;
|
|
18056
|
-
};
|
|
18057
|
-
|
|
18058
18467
|
// src/commands/project.ts
|
|
18059
18468
|
var project = command({
|
|
18060
18469
|
name: "project",
|
|
@@ -18084,6 +18493,192 @@ var project = command({
|
|
|
18084
18493
|
}
|
|
18085
18494
|
});
|
|
18086
18495
|
|
|
18496
|
+
// src/commands/projects/create.ts
|
|
18497
|
+
var create3 = command({
|
|
18498
|
+
name: "create",
|
|
18499
|
+
description: "Create a project and print its id",
|
|
18500
|
+
positionals: {
|
|
18501
|
+
name: exports_external.string().describe("Project name")
|
|
18502
|
+
},
|
|
18503
|
+
options: {
|
|
18504
|
+
summary: exports_external.string().optional().describe("One-line summary").meta({ short: "s" }),
|
|
18505
|
+
description: exports_external.string().optional().describe("Markdown description, or - for stdin").meta({ short: "d" }),
|
|
18506
|
+
status: exports_external.enum(PROJECT_STATUSES).optional().describe("Starting status (default planned)"),
|
|
18507
|
+
health: exports_external.enum(PROJECT_HEALTHS).optional().describe("Health"),
|
|
18508
|
+
target: exports_external.iso.date().optional().describe("Target date (YYYY-MM-DD)"),
|
|
18509
|
+
repo: exports_external.string().optional().describe("Repository to connect (owner/name or any remote URL)")
|
|
18510
|
+
},
|
|
18511
|
+
run: async ({ positionals: { name }, options }) => {
|
|
18512
|
+
const projectId = await (await backendClient()).mutation(api2.projects.create, {
|
|
18513
|
+
name,
|
|
18514
|
+
summary: options.summary,
|
|
18515
|
+
descriptionMarkdown: options.description === undefined ? undefined : await readTextOption(options.description),
|
|
18516
|
+
status: options.status,
|
|
18517
|
+
health: options.health,
|
|
18518
|
+
targetDate: options.target,
|
|
18519
|
+
repo: options.repo
|
|
18520
|
+
});
|
|
18521
|
+
console.log(projectId);
|
|
18522
|
+
}
|
|
18523
|
+
});
|
|
18524
|
+
|
|
18525
|
+
// src/commands/projects/get.ts
|
|
18526
|
+
var get3 = command({
|
|
18527
|
+
name: "get",
|
|
18528
|
+
description: "Show a project: properties, description, and milestones",
|
|
18529
|
+
positionals: {
|
|
18530
|
+
id: exports_external.string().describe("Project id or URL")
|
|
18531
|
+
},
|
|
18532
|
+
options: {
|
|
18533
|
+
json: exports_external.boolean().default(false).describe("Print as JSON")
|
|
18534
|
+
},
|
|
18535
|
+
run: async ({ positionals: { id }, options: { json: json2 } }) => {
|
|
18536
|
+
const client3 = await backendClient();
|
|
18537
|
+
const project2 = await resolveProject(client3, id);
|
|
18538
|
+
const milestones = await client3.query(api2.milestones.listByProject, {
|
|
18539
|
+
projectId: project2._id,
|
|
18540
|
+
today: localToday()
|
|
18541
|
+
});
|
|
18542
|
+
if (json2)
|
|
18543
|
+
return console.log(JSON.stringify({ ...project2, milestones }, null, 2));
|
|
18544
|
+
console.log(project2.name);
|
|
18545
|
+
if (project2.summary)
|
|
18546
|
+
console.log(project2.summary);
|
|
18547
|
+
if (project2.repo)
|
|
18548
|
+
console.log(`Repo: ${project2.repo}`);
|
|
18549
|
+
console.log(`Status: ${project2.status}${project2.health ? ` \xB7 Health: ${project2.health}` : ""}`);
|
|
18550
|
+
console.log(`Progress: ${project2.progress.done}/${project2.progress.total} issues done`);
|
|
18551
|
+
if (project2.targetDate)
|
|
18552
|
+
console.log(`Target date: ${project2.targetDate}${project2.isOverdue ? " (overdue)" : ""}`);
|
|
18553
|
+
if (project2.descriptionMarkdown)
|
|
18554
|
+
console.log(`
|
|
18555
|
+
${project2.descriptionMarkdown}`);
|
|
18556
|
+
if (milestones.length > 0) {
|
|
18557
|
+
console.log(`
|
|
18558
|
+
Milestones:`);
|
|
18559
|
+
for (const milestone of milestones) {
|
|
18560
|
+
const target = milestone.targetDate ? ` \xB7 target ${milestone.targetDate}${milestone.isOverdue ? " (overdue)" : ""}` : "";
|
|
18561
|
+
console.log(` ${milestone.name} \u2014 ${milestone.progress.done}/${milestone.progress.total} done${target}`);
|
|
18562
|
+
}
|
|
18563
|
+
}
|
|
18564
|
+
}
|
|
18565
|
+
});
|
|
18566
|
+
|
|
18567
|
+
// src/commands/projects/list.ts
|
|
18568
|
+
var list3 = command({
|
|
18569
|
+
name: "list",
|
|
18570
|
+
description: "List your projects, most recently updated first",
|
|
18571
|
+
options: {
|
|
18572
|
+
limit: exports_external.coerce.number().int().positive().default(50).describe("Most projects to print"),
|
|
18573
|
+
json: exports_external.boolean().default(false).describe("Print as JSON")
|
|
18574
|
+
},
|
|
18575
|
+
run: async ({ options: { limit, json: json2 } }) => {
|
|
18576
|
+
const result = await (await backendClient()).query(api2.projects.listPaginated, {
|
|
18577
|
+
paginationOpts: { numItems: limit, cursor: null },
|
|
18578
|
+
today: localToday()
|
|
18579
|
+
});
|
|
18580
|
+
if (json2)
|
|
18581
|
+
return console.log(JSON.stringify(result.page, null, 2));
|
|
18582
|
+
if (result.page.length === 0)
|
|
18583
|
+
return console.log("No projects yet.");
|
|
18584
|
+
printTable([
|
|
18585
|
+
["NAME", "STATUS", "HEALTH", "PROGRESS", "TARGET", "REPO", "ID"],
|
|
18586
|
+
...result.page.map((project2) => [
|
|
18587
|
+
project2.name,
|
|
18588
|
+
project2.status,
|
|
18589
|
+
project2.health ?? "-",
|
|
18590
|
+
`${project2.progress.done}/${project2.progress.total}`,
|
|
18591
|
+
project2.targetDate ? `${project2.targetDate}${project2.isOverdue ? " (overdue)" : ""}` : "-",
|
|
18592
|
+
project2.repo ?? "-",
|
|
18593
|
+
project2._id
|
|
18594
|
+
])
|
|
18595
|
+
]);
|
|
18596
|
+
if (!result.isDone)
|
|
18597
|
+
console.log(`
|
|
18598
|
+
More projects exist; raise --limit past ${limit}.`);
|
|
18599
|
+
}
|
|
18600
|
+
});
|
|
18601
|
+
|
|
18602
|
+
// src/commands/projects/remove.ts
|
|
18603
|
+
var remove3 = command({
|
|
18604
|
+
name: "delete",
|
|
18605
|
+
description: "Delete a project and its milestones (its issues survive, projectless)",
|
|
18606
|
+
positionals: {
|
|
18607
|
+
id: exports_external.string().describe("Project id or URL")
|
|
18608
|
+
},
|
|
18609
|
+
run: async ({ positionals: { id } }) => {
|
|
18610
|
+
const client3 = await backendClient();
|
|
18611
|
+
const project2 = await resolveProject(client3, id);
|
|
18612
|
+
await client3.mutation(api2.projects.remove, { id: project2._id });
|
|
18613
|
+
console.log(`Deleted ${project2.name}.`);
|
|
18614
|
+
}
|
|
18615
|
+
});
|
|
18616
|
+
|
|
18617
|
+
// src/commands/projects/set.ts
|
|
18618
|
+
var set3 = command({
|
|
18619
|
+
name: "set",
|
|
18620
|
+
description: "Set a project's status, health, target date, or repository",
|
|
18621
|
+
positionals: {
|
|
18622
|
+
id: exports_external.string().describe("Project id or URL")
|
|
18623
|
+
},
|
|
18624
|
+
options: {
|
|
18625
|
+
status: exports_external.enum(PROJECT_STATUSES).optional().describe("New status").meta({ short: "s" }),
|
|
18626
|
+
health: exports_external.enum(PROJECT_HEALTHS).nullable().optional().describe("New health, or --no-health to clear").meta({ negatable: true }),
|
|
18627
|
+
target: exports_external.iso.date().nullable().optional().describe("Target date (YYYY-MM-DD), or --no-target to clear").meta({ negatable: true }),
|
|
18628
|
+
repo: exports_external.string().nullable().optional().describe("Repository to connect (owner/name or any remote URL), or --no-repo to disconnect").meta({ negatable: true })
|
|
18629
|
+
},
|
|
18630
|
+
run: async ({ positionals: { id }, options }) => {
|
|
18631
|
+
const { status: status2, health, target, repo } = options;
|
|
18632
|
+
if (Object.values(options).every((value) => value === undefined))
|
|
18633
|
+
throw new Error("Nothing to set. Pass --status, --health, --target, or --repo.");
|
|
18634
|
+
const client3 = await backendClient();
|
|
18635
|
+
const project2 = await resolveProject(client3, id);
|
|
18636
|
+
if (repo !== undefined)
|
|
18637
|
+
await client3.mutation(api2.projects.setRepo, { id: project2._id, repo: repo ?? undefined });
|
|
18638
|
+
if (status2 !== undefined)
|
|
18639
|
+
await client3.mutation(api2.projects.setStatus, { id: project2._id, status: status2 });
|
|
18640
|
+
if (health !== undefined)
|
|
18641
|
+
await client3.mutation(api2.projects.setHealth, { id: project2._id, health: health ?? undefined });
|
|
18642
|
+
if (target !== undefined)
|
|
18643
|
+
await client3.mutation(api2.projects.setTargetDate, { id: project2._id, targetDate: target ?? undefined });
|
|
18644
|
+
console.log(`Updated ${project2.name}.`);
|
|
18645
|
+
}
|
|
18646
|
+
});
|
|
18647
|
+
|
|
18648
|
+
// src/commands/projects/update.ts
|
|
18649
|
+
var update3 = command({
|
|
18650
|
+
name: "update",
|
|
18651
|
+
description: "Rewrite a project's name, summary, or description",
|
|
18652
|
+
positionals: {
|
|
18653
|
+
id: exports_external.string().describe("Project id or URL")
|
|
18654
|
+
},
|
|
18655
|
+
options: {
|
|
18656
|
+
name: exports_external.string().optional().describe("New name").meta({ short: "n" }),
|
|
18657
|
+
summary: exports_external.string().optional().describe("New one-line summary").meta({ short: "s" }),
|
|
18658
|
+
description: exports_external.string().optional().describe("New markdown description, or - for stdin").meta({ short: "d" })
|
|
18659
|
+
},
|
|
18660
|
+
run: async ({ positionals: { id }, options: { name, summary, description } }) => {
|
|
18661
|
+
if (name === undefined && summary === undefined && description === undefined)
|
|
18662
|
+
throw new Error("Nothing to update. Pass --name, --summary, or --description.");
|
|
18663
|
+
const client3 = await backendClient();
|
|
18664
|
+
const project2 = await resolveProject(client3, id);
|
|
18665
|
+
await client3.mutation(api2.projects.update, {
|
|
18666
|
+
id: project2._id,
|
|
18667
|
+
name,
|
|
18668
|
+
summary,
|
|
18669
|
+
descriptionMarkdown: description === undefined ? undefined : await readTextOption(description)
|
|
18670
|
+
});
|
|
18671
|
+
console.log(`Updated ${name ?? project2.name}.`);
|
|
18672
|
+
}
|
|
18673
|
+
});
|
|
18674
|
+
|
|
18675
|
+
// src/commands/projects/index.ts
|
|
18676
|
+
var projects = group({
|
|
18677
|
+
name: "projects",
|
|
18678
|
+
description: "Manage projects",
|
|
18679
|
+
commands: [create3, list3, get3, update3, set3, remove3]
|
|
18680
|
+
});
|
|
18681
|
+
|
|
18087
18682
|
// src/commands/upgrade.ts
|
|
18088
18683
|
var upgrade = command({
|
|
18089
18684
|
name: "upgrade",
|
|
@@ -18099,7 +18694,7 @@ var upgrade = command({
|
|
|
18099
18694
|
var rootCommand = group({
|
|
18100
18695
|
name: "kds",
|
|
18101
18696
|
description: "KDS CLI",
|
|
18102
|
-
commands: [auth, pages, project, upgrade],
|
|
18697
|
+
commands: [auth, issues, pages, project, projects, upgrade],
|
|
18103
18698
|
options: {
|
|
18104
18699
|
version: exports_external.boolean().default(false).describe("Show the version").meta({ short: "v" })
|
|
18105
18700
|
},
|