nixelo 0.0.1
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/README.md +98 -0
- package/dist/commands/apiKeys.d.ts +3 -0
- package/dist/commands/apiKeys.d.ts.map +1 -0
- package/dist/commands/apiKeys.js +200 -0
- package/dist/commands/apiKeys.js.map +1 -0
- package/dist/commands/auth.d.ts +7 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +201 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/automations.d.ts +3 -0
- package/dist/commands/automations.d.ts.map +1 -0
- package/dist/commands/automations.js +302 -0
- package/dist/commands/automations.js.map +1 -0
- package/dist/commands/commandTree.d.ts +10 -0
- package/dist/commands/commandTree.d.ts.map +1 -0
- package/dist/commands/commandTree.js +36 -0
- package/dist/commands/commandTree.js.map +1 -0
- package/dist/commands/completion.d.ts +6 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +101 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +133 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/documents.d.ts +3 -0
- package/dist/commands/documents.d.ts.map +1 -0
- package/dist/commands/documents.js +200 -0
- package/dist/commands/documents.js.map +1 -0
- package/dist/commands/fields.d.ts +3 -0
- package/dist/commands/fields.d.ts.map +1 -0
- package/dist/commands/fields.js +199 -0
- package/dist/commands/fields.js.map +1 -0
- package/dist/commands/filters.d.ts +3 -0
- package/dist/commands/filters.d.ts.map +1 -0
- package/dist/commands/filters.js +243 -0
- package/dist/commands/filters.js.map +1 -0
- package/dist/commands/global.d.ts +5 -0
- package/dist/commands/global.d.ts.map +1 -0
- package/dist/commands/global.js +42 -0
- package/dist/commands/global.js.map +1 -0
- package/dist/commands/index.d.ts +23 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +23 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/invites.d.ts +3 -0
- package/dist/commands/invites.d.ts.map +1 -0
- package/dist/commands/invites.js +113 -0
- package/dist/commands/invites.js.map +1 -0
- package/dist/commands/issues/filter.d.ts +19 -0
- package/dist/commands/issues/filter.d.ts.map +1 -0
- package/dist/commands/issues/filter.js +72 -0
- package/dist/commands/issues/filter.js.map +1 -0
- package/dist/commands/issues/options.d.ts +33 -0
- package/dist/commands/issues/options.d.ts.map +1 -0
- package/dist/commands/issues/options.js +68 -0
- package/dist/commands/issues/options.js.map +1 -0
- package/dist/commands/issues/output.d.ts +12 -0
- package/dist/commands/issues/output.d.ts.map +1 -0
- package/dist/commands/issues/output.js +285 -0
- package/dist/commands/issues/output.js.map +1 -0
- package/dist/commands/issues.d.ts +3 -0
- package/dist/commands/issues.d.ts.map +1 -0
- package/dist/commands/issues.js +483 -0
- package/dist/commands/issues.js.map +1 -0
- package/dist/commands/labels.d.ts +3 -0
- package/dist/commands/labels.d.ts.map +1 -0
- package/dist/commands/labels.js +222 -0
- package/dist/commands/labels.js.map +1 -0
- package/dist/commands/notifications.d.ts +3 -0
- package/dist/commands/notifications.d.ts.map +1 -0
- package/dist/commands/notifications.js +127 -0
- package/dist/commands/notifications.js.map +1 -0
- package/dist/commands/projects.d.ts +5 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/commands/projects.js +749 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/registerAll.d.ts +3 -0
- package/dist/commands/registerAll.d.ts.map +1 -0
- package/dist/commands/registerAll.js +54 -0
- package/dist/commands/registerAll.js.map +1 -0
- package/dist/commands/sprints.d.ts +3 -0
- package/dist/commands/sprints.d.ts.map +1 -0
- package/dist/commands/sprints.js +359 -0
- package/dist/commands/sprints.js.map +1 -0
- package/dist/commands/stats.d.ts +3 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +206 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/teams.d.ts +3 -0
- package/dist/commands/teams.d.ts.map +1 -0
- package/dist/commands/teams.js +435 -0
- package/dist/commands/teams.js.map +1 -0
- package/dist/commands/templates.d.ts +3 -0
- package/dist/commands/templates.d.ts.map +1 -0
- package/dist/commands/templates.js +264 -0
- package/dist/commands/templates.js.map +1 -0
- package/dist/commands/time.d.ts +3 -0
- package/dist/commands/time.d.ts.map +1 -0
- package/dist/commands/time.js +191 -0
- package/dist/commands/time.js.map +1 -0
- package/dist/commands/tokens.d.ts +3 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +76 -0
- package/dist/commands/tokens.js.map +1 -0
- package/dist/commands/users.d.ts +5 -0
- package/dist/commands/users.d.ts.map +1 -0
- package/dist/commands/users.js +160 -0
- package/dist/commands/users.js.map +1 -0
- package/dist/commands/version.d.ts +3 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +17 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/commands/webhooks.d.ts +3 -0
- package/dist/commands/webhooks.d.ts.map +1 -0
- package/dist/commands/webhooks.js +407 -0
- package/dist/commands/webhooks.js.map +1 -0
- package/dist/commands/workspaces.d.ts +3 -0
- package/dist/commands/workspaces.d.ts.map +1 -0
- package/dist/commands/workspaces.js +424 -0
- package/dist/commands/workspaces.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/auth.d.ts +20 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +80 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/convex.d.ts +1243 -0
- package/dist/lib/convex.d.ts.map +1 -0
- package/dist/lib/convex.js +675 -0
- package/dist/lib/convex.js.map +1 -0
- package/dist/lib/options.d.ts +10 -0
- package/dist/lib/options.d.ts.map +1 -0
- package/dist/lib/options.js +79 -0
- package/dist/lib/options.js.map +1 -0
- package/dist/lib/output.d.ts +19 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +60 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/project-key.d.ts +5 -0
- package/dist/lib/project-key.d.ts.map +1 -0
- package/dist/lib/project-key.js +56 -0
- package/dist/lib/project-key.js.map +1 -0
- package/dist/lib/prompt.d.ts +5 -0
- package/dist/lib/prompt.d.ts.map +1 -0
- package/dist/lib/prompt.js +28 -0
- package/dist/lib/prompt.js.map +1 -0
- package/dist/lib/user-picker.d.ts +4 -0
- package/dist/lib/user-picker.d.ts.map +1 -0
- package/dist/lib/user-picker.js +57 -0
- package/dist/lib/user-picker.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import { addProjectMember, createProject, deleteProject, getProject, getProjectStats, getWorkspace, listProjectMembers, listProjects, listProjectWorkflow, listWorkspaces, removeProjectMember, reorderProjectWorkflow, restoreProject, setProjectWorkflow, updateProject, updateProjectMemberRole } from "../lib/convex.js";
|
|
2
|
+
import { parsePositiveIntegerOption, readOptionalText, readRequiredText } from "../lib/options.js";
|
|
3
|
+
import { getActiveOutputFormat, printError, printJson, printOutput, printRows } from "../lib/output.js";
|
|
4
|
+
import { canPrompt, promptText, requireConfirmation } from "../lib/prompt.js";
|
|
5
|
+
import { resolveRequiredUserEmail } from "../lib/user-picker.js";
|
|
6
|
+
import { addOutputOptions } from "./global.js";
|
|
7
|
+
const PROJECT_CREATE_SELECTION_LIMIT = 100;
|
|
8
|
+
const PROJECT_COLUMNS = [
|
|
9
|
+
{ header: "key", value: project => project.key },
|
|
10
|
+
{ header: "name", value: project => project.name },
|
|
11
|
+
{ header: "description", value: project => project.description }
|
|
12
|
+
];
|
|
13
|
+
const PROJECT_WORKFLOW_COLUMNS = [
|
|
14
|
+
{ header: "order", value: state => state.order },
|
|
15
|
+
{ header: "id", value: state => state.id },
|
|
16
|
+
{ header: "name", value: state => state.name },
|
|
17
|
+
{ header: "category", value: state => state.category },
|
|
18
|
+
{ header: "wipLimit", value: state => state.wipLimit }
|
|
19
|
+
];
|
|
20
|
+
const PROJECT_MEMBER_COLUMNS = [
|
|
21
|
+
{ header: "userName", value: member => member.userName },
|
|
22
|
+
{ header: "userEmail", value: member => member.userEmail },
|
|
23
|
+
{ header: "role", value: member => member.role },
|
|
24
|
+
{ header: "userId", value: member => member.userId },
|
|
25
|
+
{ header: "createdAt", value: member => member.createdAt }
|
|
26
|
+
];
|
|
27
|
+
const PROJECT_DELETE_COLUMNS = [
|
|
28
|
+
{ header: "key", value: project => project.key },
|
|
29
|
+
{ header: "name", value: project => project.name },
|
|
30
|
+
{ header: "deleted", value: project => project.deleted }
|
|
31
|
+
];
|
|
32
|
+
const PROJECT_RESTORE_COLUMNS = [
|
|
33
|
+
{ header: "key", value: project => project.key },
|
|
34
|
+
{ header: "name", value: project => project.name },
|
|
35
|
+
{ header: "restored", value: project => project.restored }
|
|
36
|
+
];
|
|
37
|
+
const PROJECT_STATS_COLUMNS = [
|
|
38
|
+
{ header: "category", value: row => row.category },
|
|
39
|
+
{ header: "value", value: row => row.value },
|
|
40
|
+
{ header: "count", value: row => row.count }
|
|
41
|
+
];
|
|
42
|
+
function readProjectRole(value) {
|
|
43
|
+
const role = readRequiredText(value, "Role", "--role");
|
|
44
|
+
if (role === "admin" || role === "member" || role === "viewer")
|
|
45
|
+
return role;
|
|
46
|
+
throw new Error("Role must be admin, member, or viewer.");
|
|
47
|
+
}
|
|
48
|
+
function readOptionalNonNegativeInteger(value, label, flag) {
|
|
49
|
+
if (value === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
const parsed = Number(value);
|
|
52
|
+
if (Number.isInteger(parsed) && parsed >= 0)
|
|
53
|
+
return parsed;
|
|
54
|
+
throw new Error(`${label} must be a non-negative integer. Pass ${flag} <number>.`);
|
|
55
|
+
}
|
|
56
|
+
function readRequiredNonNegativeInteger(value, label, flag) {
|
|
57
|
+
const parsed = readOptionalNonNegativeInteger(readRequiredText(value, label, flag), label, flag);
|
|
58
|
+
if (parsed === undefined)
|
|
59
|
+
throw new Error(`${label} is required. Pass ${flag} <number>.`);
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
function readProjectWipEnforcement(value) {
|
|
63
|
+
if (value === undefined)
|
|
64
|
+
return undefined;
|
|
65
|
+
const trimmed = value.trim();
|
|
66
|
+
if (trimmed === "badge" || trimmed === "block")
|
|
67
|
+
return trimmed;
|
|
68
|
+
throw new Error("WIP enforcement must be badge or block.");
|
|
69
|
+
}
|
|
70
|
+
function readProjectBoardType(value) {
|
|
71
|
+
if (value === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const trimmed = value.trim();
|
|
74
|
+
if (trimmed === "kanban" || trimmed === "scrum")
|
|
75
|
+
return trimmed;
|
|
76
|
+
throw new Error("Board type must be kanban or scrum.");
|
|
77
|
+
}
|
|
78
|
+
function readProjectCreateVisibility(opts) {
|
|
79
|
+
if (opts.private && opts.public)
|
|
80
|
+
throw new Error("Pass either --private or --public, not both.");
|
|
81
|
+
if (opts.public)
|
|
82
|
+
return true;
|
|
83
|
+
if (opts.private)
|
|
84
|
+
return false;
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
async function resolvePromptedRequiredText(value, label, flag) {
|
|
88
|
+
const trimmed = value?.trim();
|
|
89
|
+
if (trimmed)
|
|
90
|
+
return trimmed;
|
|
91
|
+
if (canPrompt()) {
|
|
92
|
+
const prompted = await promptText(label);
|
|
93
|
+
if (prompted)
|
|
94
|
+
return prompted;
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`${label} is required. Pass ${flag} <value>.`);
|
|
97
|
+
}
|
|
98
|
+
async function resolveProjectCreateWorkspaceSlug(explicitWorkspace) {
|
|
99
|
+
const trimmed = explicitWorkspace?.trim();
|
|
100
|
+
if (trimmed)
|
|
101
|
+
return trimmed;
|
|
102
|
+
if (!canPrompt())
|
|
103
|
+
throw new Error("Workspace slug is required. Pass --workspace <value>.");
|
|
104
|
+
const workspaces = await listWorkspaces({ limit: PROJECT_CREATE_SELECTION_LIMIT });
|
|
105
|
+
if (workspaces.length === 1) {
|
|
106
|
+
const workspace = workspaces[0];
|
|
107
|
+
if (!workspace)
|
|
108
|
+
throw new Error("Workspace slug is required. Pass --workspace <value>.");
|
|
109
|
+
printOutput(`Using workspace ${workspace.slug}: ${workspace.name}`);
|
|
110
|
+
return workspace.slug;
|
|
111
|
+
}
|
|
112
|
+
if (workspaces.length > 0) {
|
|
113
|
+
printOutput("Available workspaces:");
|
|
114
|
+
for (const workspace of workspaces)
|
|
115
|
+
printOutput(` ${workspace.slug} ${workspace.name}`);
|
|
116
|
+
}
|
|
117
|
+
const prompted = await promptText("Workspace slug");
|
|
118
|
+
if (prompted)
|
|
119
|
+
return prompted;
|
|
120
|
+
throw new Error("Workspace slug is required. Pass --workspace <value>.");
|
|
121
|
+
}
|
|
122
|
+
async function resolveProjectCreateTeamSlug(explicitTeam, workspaceSlug) {
|
|
123
|
+
const team = readOptionalText(explicitTeam, "Team slug", "--team");
|
|
124
|
+
if (team !== undefined)
|
|
125
|
+
return team;
|
|
126
|
+
if (!canPrompt())
|
|
127
|
+
return undefined;
|
|
128
|
+
const workspace = await getWorkspace(workspaceSlug, { limit: PROJECT_CREATE_SELECTION_LIMIT });
|
|
129
|
+
if (workspace?.teams && workspace.teams.length > 0) {
|
|
130
|
+
printOutput("Available teams:");
|
|
131
|
+
for (const availableTeam of workspace.teams) {
|
|
132
|
+
printOutput(` ${availableTeam.slug} ${availableTeam.name}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const prompted = await promptText("Team slug (optional)");
|
|
136
|
+
return prompted || undefined;
|
|
137
|
+
}
|
|
138
|
+
async function readProjectCreateInput(opts) {
|
|
139
|
+
const workspaceSlug = await resolveProjectCreateWorkspaceSlug(opts.workspace);
|
|
140
|
+
const teamSlug = await resolveProjectCreateTeamSlug(opts.team, workspaceSlug);
|
|
141
|
+
const input = {
|
|
142
|
+
workspaceSlug,
|
|
143
|
+
key: await resolvePromptedRequiredText(opts.key, "Project key", "--key"),
|
|
144
|
+
name: await resolvePromptedRequiredText(opts.name, "Project name", "--name")
|
|
145
|
+
};
|
|
146
|
+
const boardType = readProjectBoardType(opts.boardType);
|
|
147
|
+
if (boardType !== undefined)
|
|
148
|
+
input.boardType = boardType;
|
|
149
|
+
const description = readOptionalText(opts.description, "Project description", "--description");
|
|
150
|
+
if (description !== undefined)
|
|
151
|
+
input.description = description;
|
|
152
|
+
if (teamSlug !== undefined)
|
|
153
|
+
input.teamSlug = teamSlug;
|
|
154
|
+
const isPublic = readProjectCreateVisibility(opts);
|
|
155
|
+
if (isPublic !== undefined)
|
|
156
|
+
input.isPublic = isPublic;
|
|
157
|
+
return input;
|
|
158
|
+
}
|
|
159
|
+
function readProjectUpdateInput(opts) {
|
|
160
|
+
if (opts.private && opts.public)
|
|
161
|
+
throw new Error("Pass either --private or --public, not both.");
|
|
162
|
+
const input = {};
|
|
163
|
+
const name = readOptionalText(opts.name, "Project name", "--name");
|
|
164
|
+
if (name !== undefined)
|
|
165
|
+
input.name = name;
|
|
166
|
+
const description = readOptionalText(opts.description, "Project description", "--description");
|
|
167
|
+
if (description !== undefined)
|
|
168
|
+
input.description = description;
|
|
169
|
+
if (opts.public)
|
|
170
|
+
input.isPublic = true;
|
|
171
|
+
if (opts.private)
|
|
172
|
+
input.isPublic = false;
|
|
173
|
+
const autoArchiveDays = readOptionalNonNegativeInteger(opts.autoArchiveDays, "Auto archive days", "--auto-archive-days");
|
|
174
|
+
if (autoArchiveDays !== undefined)
|
|
175
|
+
input.autoArchiveDays = autoArchiveDays;
|
|
176
|
+
const wipEnforcement = readProjectWipEnforcement(opts.wipEnforcement);
|
|
177
|
+
if (wipEnforcement !== undefined)
|
|
178
|
+
input.wipEnforcement = wipEnforcement;
|
|
179
|
+
if (Object.keys(input).length === 0)
|
|
180
|
+
throw new Error("At least one update flag is required.");
|
|
181
|
+
return input;
|
|
182
|
+
}
|
|
183
|
+
function isWorkflowCategory(value) {
|
|
184
|
+
return value === "todo" || value === "inprogress" || value === "done";
|
|
185
|
+
}
|
|
186
|
+
function readWorkflowStateEntry(entry, index) {
|
|
187
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
188
|
+
throw new Error(`Workflow state ${index} must be an object.`);
|
|
189
|
+
}
|
|
190
|
+
const state = entry;
|
|
191
|
+
if (typeof state.id !== "string" || state.id.trim() === "") {
|
|
192
|
+
throw new Error(`Workflow state ${index} requires a non-empty id.`);
|
|
193
|
+
}
|
|
194
|
+
if (typeof state.name !== "string" || state.name.trim() === "") {
|
|
195
|
+
throw new Error(`Workflow state ${index} requires a non-empty name.`);
|
|
196
|
+
}
|
|
197
|
+
if (!isWorkflowCategory(state.category)) {
|
|
198
|
+
throw new Error(`Workflow state ${index} category must be todo, inprogress, or done.`);
|
|
199
|
+
}
|
|
200
|
+
if (typeof state.order !== "number" || !Number.isFinite(state.order)) {
|
|
201
|
+
throw new Error(`Workflow state ${index} order must be a number.`);
|
|
202
|
+
}
|
|
203
|
+
if (state.wipLimit !== undefined &&
|
|
204
|
+
(typeof state.wipLimit !== "number" || !Number.isFinite(state.wipLimit))) {
|
|
205
|
+
throw new Error(`Workflow state ${index} wipLimit must be a number.`);
|
|
206
|
+
}
|
|
207
|
+
const workflowState = {
|
|
208
|
+
id: state.id,
|
|
209
|
+
name: state.name,
|
|
210
|
+
category: state.category,
|
|
211
|
+
order: state.order
|
|
212
|
+
};
|
|
213
|
+
if (state.wipLimit !== undefined)
|
|
214
|
+
workflowState.wipLimit = state.wipLimit;
|
|
215
|
+
return workflowState;
|
|
216
|
+
}
|
|
217
|
+
function readWorkflowStatesJson(value) {
|
|
218
|
+
const raw = readRequiredText(value, "Workflow states", "--states");
|
|
219
|
+
let parsed;
|
|
220
|
+
try {
|
|
221
|
+
parsed = JSON.parse(raw);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
throw new Error("Workflow states must be valid JSON.");
|
|
225
|
+
}
|
|
226
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
227
|
+
throw new Error("Workflow states must be a non-empty JSON array.");
|
|
228
|
+
}
|
|
229
|
+
return parsed.map((entry, index) => readWorkflowStateEntry(entry, index));
|
|
230
|
+
}
|
|
231
|
+
function readProjectWorkflowSetInput(opts) {
|
|
232
|
+
const input = { workflowStates: readWorkflowStatesJson(opts.states) };
|
|
233
|
+
const wipEnforcement = readProjectWipEnforcement(opts.wipEnforcement);
|
|
234
|
+
if (wipEnforcement !== undefined)
|
|
235
|
+
input.wipEnforcement = wipEnforcement;
|
|
236
|
+
return input;
|
|
237
|
+
}
|
|
238
|
+
function printEmptyProjectList(format) {
|
|
239
|
+
if (format === "json") {
|
|
240
|
+
printJson([]);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (format === "csv") {
|
|
244
|
+
printRows([], PROJECT_COLUMNS, format);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
printOutput("No projects found.");
|
|
248
|
+
}
|
|
249
|
+
function printProjectList(projects) {
|
|
250
|
+
const format = getActiveOutputFormat();
|
|
251
|
+
if (projects.length === 0) {
|
|
252
|
+
printEmptyProjectList(format);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (format !== "table") {
|
|
256
|
+
printRows(projects, PROJECT_COLUMNS, format);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
for (const p of projects)
|
|
260
|
+
printOutput(` ${p.key} ${p.name}`);
|
|
261
|
+
}
|
|
262
|
+
function printMissingProject(key, format) {
|
|
263
|
+
if (format === "json") {
|
|
264
|
+
printJson(null);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (format === "csv") {
|
|
268
|
+
printRows([], PROJECT_COLUMNS, format);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
printOutput(`Project ${key} not found.`);
|
|
272
|
+
}
|
|
273
|
+
function printMissingProjectRows(key, format, columns) {
|
|
274
|
+
if (format === "json") {
|
|
275
|
+
printJson(null);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (format === "csv") {
|
|
279
|
+
printRows([], columns, format);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
printOutput(`Project ${key} not found.`);
|
|
283
|
+
}
|
|
284
|
+
function printProjectWorkflowSection(states) {
|
|
285
|
+
if (!states || states.length === 0)
|
|
286
|
+
return;
|
|
287
|
+
printOutput("Workflow:");
|
|
288
|
+
for (const state of states) {
|
|
289
|
+
const wip = state.wipLimit === undefined ? "" : ` wip:${state.wipLimit}`;
|
|
290
|
+
printOutput(` ${state.order}. ${state.name} [${state.category}] ${state.id}${wip}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function printProjectMembersSection(members) {
|
|
294
|
+
if (!members || members.length === 0)
|
|
295
|
+
return;
|
|
296
|
+
printOutput("Members:");
|
|
297
|
+
for (const member of members) {
|
|
298
|
+
const email = member.userEmail ? ` <${member.userEmail}>` : "";
|
|
299
|
+
printOutput(` ${member.userName}${email} [${member.role}]`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function printProjectView(detail, key) {
|
|
303
|
+
const format = getActiveOutputFormat();
|
|
304
|
+
if (!detail) {
|
|
305
|
+
printMissingProject(key, format);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (format === "json") {
|
|
309
|
+
printJson(detail);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (format === "csv") {
|
|
313
|
+
printRows([detail.project], PROJECT_COLUMNS, format);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const { project } = detail;
|
|
317
|
+
printOutput(`${project.key} ${project.name}`);
|
|
318
|
+
if (project.description)
|
|
319
|
+
printOutput(project.description);
|
|
320
|
+
printProjectWorkflowSection(detail.workflow);
|
|
321
|
+
printProjectMembersSection(detail.members);
|
|
322
|
+
printProjectStatsSummary(detail.stats);
|
|
323
|
+
}
|
|
324
|
+
function printProjectMutationResult(project, key, action) {
|
|
325
|
+
const format = getActiveOutputFormat();
|
|
326
|
+
if (!project) {
|
|
327
|
+
if (action === "Created" && format === "table") {
|
|
328
|
+
printOutput(`Workspace or team for project ${key} not found.`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
printMissingProject(key, format);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (format === "json") {
|
|
335
|
+
printJson(project);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (format === "csv") {
|
|
339
|
+
printRows([project], PROJECT_COLUMNS, format);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
printOutput(`${action} project ${project.key}: ${project.name}`);
|
|
343
|
+
}
|
|
344
|
+
function printProjectDeleteResult(result, key) {
|
|
345
|
+
const format = getActiveOutputFormat();
|
|
346
|
+
if (!result) {
|
|
347
|
+
if (format === "json") {
|
|
348
|
+
printJson(null);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (format === "csv") {
|
|
352
|
+
printRows([], PROJECT_DELETE_COLUMNS, format);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
printOutput(`Project ${key} not found.`);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (format === "json") {
|
|
359
|
+
printJson(result);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (format === "csv") {
|
|
363
|
+
printRows([result], PROJECT_DELETE_COLUMNS, format);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
printOutput(`Deleted project ${result.key}: ${result.name}`);
|
|
367
|
+
}
|
|
368
|
+
function printProjectRestoreResult(result, key) {
|
|
369
|
+
const format = getActiveOutputFormat();
|
|
370
|
+
if (!result) {
|
|
371
|
+
if (format === "json") {
|
|
372
|
+
printJson(null);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (format === "csv") {
|
|
376
|
+
printRows([], PROJECT_RESTORE_COLUMNS, format);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
printOutput(`Project ${key} not found.`);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (format === "json") {
|
|
383
|
+
printJson(result);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (format === "csv") {
|
|
387
|
+
printRows([result], PROJECT_RESTORE_COLUMNS, format);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
printOutput(`Restored project ${result.key}: ${result.name}`);
|
|
391
|
+
}
|
|
392
|
+
function printProjectWorkflowResult(states, key) {
|
|
393
|
+
const format = getActiveOutputFormat();
|
|
394
|
+
if (!states) {
|
|
395
|
+
printMissingProjectRows(key, format, PROJECT_WORKFLOW_COLUMNS);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (states.length === 0) {
|
|
399
|
+
if (format === "json") {
|
|
400
|
+
printJson([]);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (format === "csv") {
|
|
404
|
+
printRows(states, PROJECT_WORKFLOW_COLUMNS, format);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
printOutput("No workflow states found.");
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (format !== "table") {
|
|
411
|
+
printRows(states, PROJECT_WORKFLOW_COLUMNS, format);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
for (const state of states) {
|
|
415
|
+
const wip = state.wipLimit === undefined ? "" : ` wip:${state.wipLimit}`;
|
|
416
|
+
printOutput(` ${state.order}. ${state.name} [${state.category}] ${state.id}${wip}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function printProjectMembersResult(members, key) {
|
|
420
|
+
const format = getActiveOutputFormat();
|
|
421
|
+
if (!members) {
|
|
422
|
+
printMissingProjectRows(key, format, PROJECT_MEMBER_COLUMNS);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (members.length === 0) {
|
|
426
|
+
if (format === "json") {
|
|
427
|
+
printJson([]);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (format === "csv") {
|
|
431
|
+
printRows(members, PROJECT_MEMBER_COLUMNS, format);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
printOutput("No project members found.");
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (format !== "table") {
|
|
438
|
+
printRows(members, PROJECT_MEMBER_COLUMNS, format);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
for (const member of members) {
|
|
442
|
+
const email = member.userEmail ? ` <${member.userEmail}>` : "";
|
|
443
|
+
printOutput(` ${member.userName}${email} [${member.role}]`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function printProjectMemberMutationResult(result, key, action) {
|
|
447
|
+
const format = getActiveOutputFormat();
|
|
448
|
+
if (!result) {
|
|
449
|
+
printMissingProjectRows(key, format, PROJECT_MEMBER_COLUMNS);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (format === "json") {
|
|
453
|
+
printJson(result);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (format === "csv") {
|
|
457
|
+
printRows([result], PROJECT_MEMBER_COLUMNS, format);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const email = result.userEmail ? ` <${result.userEmail}>` : "";
|
|
461
|
+
const suffix = action === "Removed" ? ` from ${result.projectKey}` : ` in ${result.projectKey}`;
|
|
462
|
+
printOutput(`${action} ${result.userName}${email}${suffix} [${result.role}]`);
|
|
463
|
+
}
|
|
464
|
+
function flattenStatsBucket(category, bucket) {
|
|
465
|
+
return bucket.map(entry => ({ category, value: entry.value, count: entry.count }));
|
|
466
|
+
}
|
|
467
|
+
function flattenProjectStats(stats) {
|
|
468
|
+
return [
|
|
469
|
+
...flattenStatsBucket("status", stats.byStatus),
|
|
470
|
+
...flattenStatsBucket("priority", stats.byPriority),
|
|
471
|
+
...flattenStatsBucket("type", stats.byType),
|
|
472
|
+
...flattenStatsBucket("assignee", stats.byAssignee)
|
|
473
|
+
];
|
|
474
|
+
}
|
|
475
|
+
function printStatsBucket(label, bucket, prefix = "") {
|
|
476
|
+
printOutput(`${prefix}${label}:`);
|
|
477
|
+
for (const entry of bucket) {
|
|
478
|
+
printOutput(`${prefix} ${entry.value}: ${entry.count}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function printProjectStatsSummary(stats) {
|
|
482
|
+
if (!stats)
|
|
483
|
+
return;
|
|
484
|
+
const truncated = stats.truncated ? " (truncated)" : "";
|
|
485
|
+
printOutput("Stats:");
|
|
486
|
+
printOutput(` Total issues: ${stats.totalIssues}${truncated}`);
|
|
487
|
+
printStatsBucket("Status", stats.byStatus, " ");
|
|
488
|
+
printStatsBucket("Priority", stats.byPriority, " ");
|
|
489
|
+
printStatsBucket("Type", stats.byType, " ");
|
|
490
|
+
printStatsBucket("Assignee", stats.byAssignee, " ");
|
|
491
|
+
}
|
|
492
|
+
export function printProjectStatsResult(stats, key) {
|
|
493
|
+
const format = getActiveOutputFormat();
|
|
494
|
+
if (!stats) {
|
|
495
|
+
printMissingProjectRows(key, format, PROJECT_STATS_COLUMNS);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (format === "json") {
|
|
499
|
+
printJson(stats);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (format === "csv") {
|
|
503
|
+
printRows(flattenProjectStats(stats), PROJECT_STATS_COLUMNS, format);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const truncated = stats.truncated ? " (truncated)" : "";
|
|
507
|
+
printOutput(`${stats.key} ${stats.name}`);
|
|
508
|
+
printOutput(`Total issues: ${stats.totalIssues}${truncated}`);
|
|
509
|
+
printStatsBucket("Status", stats.byStatus);
|
|
510
|
+
printStatsBucket("Priority", stats.byPriority);
|
|
511
|
+
printStatsBucket("Type", stats.byType);
|
|
512
|
+
printStatsBucket("Assignee", stats.byAssignee);
|
|
513
|
+
}
|
|
514
|
+
export function registerProjectsCommands(program) {
|
|
515
|
+
const projectsCmd = program.command("projects").description("Manage projects");
|
|
516
|
+
addOutputOptions(projectsCmd.command("list").description("List projects")).action(async () => {
|
|
517
|
+
try {
|
|
518
|
+
printProjectList(await listProjects());
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
addOutputOptions(projectsCmd
|
|
526
|
+
.command("view")
|
|
527
|
+
.argument("<key>", "Project key (e.g. ENG)")
|
|
528
|
+
.option("-n, --limit <number>", "Max members to show", "20")
|
|
529
|
+
.description("View project details")).action(async (key, opts) => {
|
|
530
|
+
try {
|
|
531
|
+
const project = await getProject(key);
|
|
532
|
+
if (!project) {
|
|
533
|
+
printProjectView(null, key);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const memberLimit = parsePositiveIntegerOption(opts.limit);
|
|
537
|
+
const [workflow, members, stats] = await Promise.all([
|
|
538
|
+
listProjectWorkflow(key),
|
|
539
|
+
listProjectMembers(key, { limit: memberLimit }),
|
|
540
|
+
getProjectStats(key)
|
|
541
|
+
]);
|
|
542
|
+
printProjectView({ members, project, stats, workflow }, key);
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
546
|
+
process.exit(1);
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
addOutputOptions(projectsCmd.command("create"))
|
|
550
|
+
.option("--workspace <slug>", "Workspace slug")
|
|
551
|
+
.option("--team <slug>", "Team slug")
|
|
552
|
+
.option("--key <key>", "Project key")
|
|
553
|
+
.option("--name <name>", "Project name")
|
|
554
|
+
.option("--description <description>", "Project description")
|
|
555
|
+
.option("--board-type <type>", "Board type: kanban or scrum")
|
|
556
|
+
.option("--public", "Make project visible to organization members")
|
|
557
|
+
.option("--private", "Make project private")
|
|
558
|
+
.description("Create a project")
|
|
559
|
+
.action(async (opts) => {
|
|
560
|
+
try {
|
|
561
|
+
const input = await readProjectCreateInput(opts);
|
|
562
|
+
printProjectMutationResult(await createProject(input), input.key, "Created");
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
addOutputOptions(projectsCmd.command("update"))
|
|
570
|
+
.argument("<key>", "Project key (e.g. ENG)")
|
|
571
|
+
.option("--name <name>", "Project name")
|
|
572
|
+
.option("--description <description>", "Project description")
|
|
573
|
+
.option("--public", "Make project visible to organization members")
|
|
574
|
+
.option("--private", "Make project private")
|
|
575
|
+
.option("--auto-archive-days <number>", "Auto-archive completed issues after N days; 0 disables")
|
|
576
|
+
.option("--wip-enforcement <mode>", "WIP enforcement: badge or block")
|
|
577
|
+
.description("Update project settings")
|
|
578
|
+
.action(async (key, opts) => {
|
|
579
|
+
try {
|
|
580
|
+
const projectKey = readRequiredText(key, "Project key", "<key>");
|
|
581
|
+
printProjectMutationResult(await updateProject(projectKey, readProjectUpdateInput(opts)), projectKey, "Updated");
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
addOutputOptions(projectsCmd.command("delete"))
|
|
589
|
+
.argument("<key>", "Project key (e.g. ENG)")
|
|
590
|
+
.option("-y, --yes", "Confirm project deletion")
|
|
591
|
+
.description("Delete a project")
|
|
592
|
+
.action(async (key, opts) => {
|
|
593
|
+
try {
|
|
594
|
+
const projectKey = readRequiredText(key, "Project key", "<key>");
|
|
595
|
+
await requireConfirmation(`Delete project ${projectKey}?`, opts.yes === true);
|
|
596
|
+
printProjectDeleteResult(await deleteProject(projectKey), projectKey);
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
600
|
+
process.exit(1);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
addOutputOptions(projectsCmd.command("restore"))
|
|
604
|
+
.argument("<key>", "Project key (e.g. ENG)")
|
|
605
|
+
.description("Restore a deleted project")
|
|
606
|
+
.action(async (key) => {
|
|
607
|
+
try {
|
|
608
|
+
const projectKey = readRequiredText(key, "Project key", "<key>");
|
|
609
|
+
printProjectRestoreResult(await restoreProject(projectKey), projectKey);
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
613
|
+
process.exit(1);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
const workflowCmd = projectsCmd
|
|
617
|
+
.command("workflow")
|
|
618
|
+
.argument("[key]", "Project key (e.g. ENG)")
|
|
619
|
+
.description("Show or manage project workflow states");
|
|
620
|
+
addOutputOptions(workflowCmd).action(async (key) => {
|
|
621
|
+
try {
|
|
622
|
+
const projectKey = readRequiredText(key, "Project key", "<key>");
|
|
623
|
+
printProjectWorkflowResult(await listProjectWorkflow(projectKey), projectKey);
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
627
|
+
process.exit(1);
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
addOutputOptions(workflowCmd.command("set"))
|
|
631
|
+
.argument("<key>", "Project key (e.g. ENG)")
|
|
632
|
+
.requiredOption("--states <json>", "Workflow states JSON array")
|
|
633
|
+
.option("--wip-enforcement <mode>", "WIP enforcement: badge or block")
|
|
634
|
+
.description("Replace project workflow states")
|
|
635
|
+
.action(async (key, opts) => {
|
|
636
|
+
try {
|
|
637
|
+
const projectKey = readRequiredText(key, "Project key", "<key>");
|
|
638
|
+
printProjectWorkflowResult(await setProjectWorkflow(projectKey, readProjectWorkflowSetInput(opts)), projectKey);
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
642
|
+
process.exit(1);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
addOutputOptions(workflowCmd.command("reorder"))
|
|
646
|
+
.argument("<key>", "Project key (e.g. ENG)")
|
|
647
|
+
.requiredOption("--id <stateId>", "Workflow state id to move")
|
|
648
|
+
.requiredOption("--position <number>", "Zero-based target position")
|
|
649
|
+
.description("Move a workflow state to a new position")
|
|
650
|
+
.action(async (key, opts) => {
|
|
651
|
+
try {
|
|
652
|
+
const projectKey = readRequiredText(key, "Project key", "<key>");
|
|
653
|
+
printProjectWorkflowResult(await reorderProjectWorkflow(projectKey, {
|
|
654
|
+
stateId: readRequiredText(opts.id, "Workflow state id", "--id"),
|
|
655
|
+
position: readRequiredNonNegativeInteger(opts.position, "Position", "--position")
|
|
656
|
+
}), projectKey);
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
const membersCmd = projectsCmd
|
|
664
|
+
.command("members")
|
|
665
|
+
.argument("[key]", "Project key (e.g. ENG)")
|
|
666
|
+
.option("-n, --limit <number>", "Max members", "20")
|
|
667
|
+
.description("List or manage project members");
|
|
668
|
+
addOutputOptions(membersCmd).action(async (key, opts) => {
|
|
669
|
+
try {
|
|
670
|
+
const projectKey = readRequiredText(key, "Project key", "<key>");
|
|
671
|
+
printProjectMembersResult(await listProjectMembers(projectKey, { limit: parsePositiveIntegerOption(opts.limit) }), projectKey);
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
addOutputOptions(membersCmd.command("add"))
|
|
679
|
+
.argument("<key>", "Project key (e.g. ENG)")
|
|
680
|
+
.option("--email <email>", "Member email; prompts when omitted")
|
|
681
|
+
.requiredOption("--role <role>", "Member role: admin, member, or viewer")
|
|
682
|
+
.description("Add a project member")
|
|
683
|
+
.action(async (key, opts) => {
|
|
684
|
+
try {
|
|
685
|
+
const projectKey = readRequiredText(key, "Project key", "<key>");
|
|
686
|
+
printProjectMemberMutationResult(await addProjectMember({
|
|
687
|
+
projectKey,
|
|
688
|
+
email: await resolveRequiredUserEmail(opts.email),
|
|
689
|
+
role: readProjectRole(opts.role)
|
|
690
|
+
}), projectKey, "Added");
|
|
691
|
+
}
|
|
692
|
+
catch (error) {
|
|
693
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
694
|
+
process.exit(1);
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
addOutputOptions(membersCmd.command("remove"))
|
|
698
|
+
.argument("<key>", "Project key (e.g. ENG)")
|
|
699
|
+
.option("--email <email>", "Member email; prompts when omitted")
|
|
700
|
+
.option("-y, --yes", "Confirm project member removal")
|
|
701
|
+
.description("Remove a project member")
|
|
702
|
+
.action(async (key, opts) => {
|
|
703
|
+
try {
|
|
704
|
+
const projectKey = readRequiredText(key, "Project key", "<key>");
|
|
705
|
+
const email = await resolveRequiredUserEmail(opts.email);
|
|
706
|
+
await requireConfirmation(`Remove ${email} from project ${projectKey}?`, opts.yes === true);
|
|
707
|
+
printProjectMemberMutationResult(await removeProjectMember({
|
|
708
|
+
projectKey,
|
|
709
|
+
email
|
|
710
|
+
}), projectKey, "Removed");
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
addOutputOptions(membersCmd.command("role"))
|
|
718
|
+
.argument("<key>", "Project key (e.g. ENG)")
|
|
719
|
+
.option("--email <email>", "Member email; prompts when omitted")
|
|
720
|
+
.requiredOption("--role <role>", "Member role: admin, member, or viewer")
|
|
721
|
+
.description("Update a project member role")
|
|
722
|
+
.action(async (key, opts) => {
|
|
723
|
+
try {
|
|
724
|
+
const projectKey = readRequiredText(key, "Project key", "<key>");
|
|
725
|
+
printProjectMemberMutationResult(await updateProjectMemberRole({
|
|
726
|
+
projectKey,
|
|
727
|
+
email: await resolveRequiredUserEmail(opts.email),
|
|
728
|
+
role: readProjectRole(opts.role)
|
|
729
|
+
}), projectKey, "Updated");
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
733
|
+
process.exit(1);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
addOutputOptions(projectsCmd
|
|
737
|
+
.command("stats")
|
|
738
|
+
.argument("<key>", "Project key (e.g. ENG)")
|
|
739
|
+
.description("Show project issue stats")).action(async (key) => {
|
|
740
|
+
try {
|
|
741
|
+
printProjectStatsResult(await getProjectStats(key), key);
|
|
742
|
+
}
|
|
743
|
+
catch (error) {
|
|
744
|
+
printError("Failed:", error instanceof Error ? error.message : error);
|
|
745
|
+
process.exit(1);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
//# sourceMappingURL=projects.js.map
|