@reximo/cli 0.1.0-alpha.3 → 0.1.0-alpha.5
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 +162 -0
- package/package.json +3 -3
- package/src/client/generated/index.d.ts +1 -1
- package/src/client/generated/index.js +2 -4
- package/src/client/generated/index.js.map +1 -1
- package/src/client/generated/reximo-api-client.generated.d.ts +253 -0
- package/src/client/generated/reximo-api-client.generated.js +285 -0
- package/src/client/generated/reximo-api-client.generated.js.map +1 -1
- package/src/client/reximo-client.d.ts +113 -1
- package/src/client/reximo-client.js +262 -0
- package/src/client/reximo-client.js.map +1 -1
- package/src/lib/run-cli.js +610 -0
- package/src/lib/run-cli.js.map +1 -1
- package/src/lib/runtime-deps.d.ts +10 -0
- package/src/lib/runtime-deps.js +80 -0
- package/src/lib/runtime-deps.js.map +1 -0
package/src/lib/run-cli.js
CHANGED
|
@@ -33,6 +33,12 @@ function getOutputMode(command) {
|
|
|
33
33
|
const opts = command.optsWithGlobals();
|
|
34
34
|
return opts.json ? "json" /* OutputMode.Json */ : "text" /* OutputMode.Text */;
|
|
35
35
|
}
|
|
36
|
+
function parseCsvOption(value) {
|
|
37
|
+
return value
|
|
38
|
+
.split(',')
|
|
39
|
+
.map((entry) => entry.trim())
|
|
40
|
+
.filter((entry) => entry.length > 0);
|
|
41
|
+
}
|
|
36
42
|
function renderConfigSummary(config, includeSensitive) {
|
|
37
43
|
return {
|
|
38
44
|
profile: config.profile,
|
|
@@ -69,6 +75,15 @@ function requireConfiguredOrganization(config) {
|
|
|
69
75
|
}
|
|
70
76
|
return config.organizationId;
|
|
71
77
|
}
|
|
78
|
+
function createAuthenticatedContext(dependencies, configOptions) {
|
|
79
|
+
const config = (0, config_1.loadResolvedConfig)(configOptions);
|
|
80
|
+
return {
|
|
81
|
+
config,
|
|
82
|
+
client: dependencies.clientFactory(config),
|
|
83
|
+
accessToken: requireConfiguredToken(config, "accessToken" /* ConfiguredTokenField.AccessToken */),
|
|
84
|
+
organizationId: requireConfiguredOrganization(config)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
72
87
|
async function resolveRequiredValue(currentValue, label, prompt) {
|
|
73
88
|
if (currentValue?.trim()) {
|
|
74
89
|
return currentValue.trim();
|
|
@@ -124,6 +139,214 @@ async function resolveOrganizationContext(promptAdapter, organizations, organiza
|
|
|
124
139
|
const selectedOrganizationId = await promptAdapter.select('Select organization', choices);
|
|
125
140
|
return (activeOrganizations.find((organization) => organization.organizationId === selectedOrganizationId) ?? null);
|
|
126
141
|
}
|
|
142
|
+
function formatIssueIdentifier(issue) {
|
|
143
|
+
if (issue.project?.key) {
|
|
144
|
+
return `${issue.project.key}-${issue.issueNumber}`;
|
|
145
|
+
}
|
|
146
|
+
return `#${issue.issueNumber}`;
|
|
147
|
+
}
|
|
148
|
+
function formatIssueLines(result) {
|
|
149
|
+
if (result.data.length === 0) {
|
|
150
|
+
return ['No issues found.'];
|
|
151
|
+
}
|
|
152
|
+
const lines = [
|
|
153
|
+
`Issues: ${result.metadata.pagination.total} total (page ${result.metadata.pagination.page}/${Math.max(result.metadata.pagination.totalPages, 1)})`
|
|
154
|
+
];
|
|
155
|
+
for (const issue of result.data) {
|
|
156
|
+
lines.push(`${formatIssueIdentifier(issue)} [id:${issue.id}] ${issue.title} (${issue.status}, ${issue.priority})`);
|
|
157
|
+
}
|
|
158
|
+
return lines;
|
|
159
|
+
}
|
|
160
|
+
function formatIssueDetailLines(issue) {
|
|
161
|
+
return [
|
|
162
|
+
`Issue: ${formatIssueIdentifier(issue)} [id:${issue.id}]`,
|
|
163
|
+
`Title: ${issue.title}`,
|
|
164
|
+
`Status: ${issue.status}`,
|
|
165
|
+
`Priority: ${issue.priority}`,
|
|
166
|
+
`Project: ${issue.project?.name ?? '(none)'}`,
|
|
167
|
+
`Updated: ${issue.updatedAt}`
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
function formatProjectAccess(project) {
|
|
171
|
+
if (project.visibility === 'public') {
|
|
172
|
+
return 'public';
|
|
173
|
+
}
|
|
174
|
+
return project.isMember ? 'private member' : 'private';
|
|
175
|
+
}
|
|
176
|
+
function formatProjectLines(result) {
|
|
177
|
+
if (result.data.length === 0) {
|
|
178
|
+
return ['No projects found.'];
|
|
179
|
+
}
|
|
180
|
+
const lines = [
|
|
181
|
+
`Projects: ${result.metadata.pagination.total} total (page ${result.metadata.pagination.page}/${Math.max(result.metadata.pagination.totalPages, 1)})`
|
|
182
|
+
];
|
|
183
|
+
for (const project of result.data) {
|
|
184
|
+
lines.push(`${project.key} [id:${project.id}] ${project.name} (${formatProjectAccess(project)})`);
|
|
185
|
+
}
|
|
186
|
+
return lines;
|
|
187
|
+
}
|
|
188
|
+
function formatContentLines(result) {
|
|
189
|
+
if (result.data.length === 0) {
|
|
190
|
+
return ['No content entries found.'];
|
|
191
|
+
}
|
|
192
|
+
return [
|
|
193
|
+
`Content entries: ${result.data.length}`,
|
|
194
|
+
...result.data.map((content) => `${content.slug} [id:${content.id}] ${content.title} (revision ${content.revision})`)
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
function formatContentDetailLines(content) {
|
|
198
|
+
return [
|
|
199
|
+
`Content: ${content.slug} [id:${content.id}]`,
|
|
200
|
+
`Title: ${content.title}`,
|
|
201
|
+
`Revision: ${content.revision}`,
|
|
202
|
+
`Project: ${content.projectId ?? '(none)'}`,
|
|
203
|
+
`Parent: ${content.parentId ?? '(none)'}`,
|
|
204
|
+
`Updated: ${content.updatedAt}`
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
function buildUpdateIssuePayload(options) {
|
|
208
|
+
const payload = {};
|
|
209
|
+
if (options.title !== undefined) {
|
|
210
|
+
payload['title'] = options.title;
|
|
211
|
+
}
|
|
212
|
+
if (options.descriptionMarkdown !== undefined) {
|
|
213
|
+
payload['descriptionMarkdown'] = options.descriptionMarkdown;
|
|
214
|
+
}
|
|
215
|
+
if (options.status !== undefined) {
|
|
216
|
+
payload['status'] = options.status;
|
|
217
|
+
}
|
|
218
|
+
if (options.statusDefinitionId !== undefined) {
|
|
219
|
+
payload['statusDefinitionId'] = options.statusDefinitionId;
|
|
220
|
+
}
|
|
221
|
+
if (options.priority !== undefined) {
|
|
222
|
+
payload['priority'] = options.priority;
|
|
223
|
+
}
|
|
224
|
+
if (options.position !== undefined) {
|
|
225
|
+
payload['position'] = options.position;
|
|
226
|
+
}
|
|
227
|
+
if (options.baseRevision !== undefined) {
|
|
228
|
+
payload['baseRevision'] = options.baseRevision;
|
|
229
|
+
}
|
|
230
|
+
if (options.clearParentIssue) {
|
|
231
|
+
payload['parentIssueId'] = null;
|
|
232
|
+
}
|
|
233
|
+
else if (options.parentIssueId !== undefined) {
|
|
234
|
+
payload['parentIssueId'] = options.parentIssueId;
|
|
235
|
+
}
|
|
236
|
+
if (options.clearEstimate) {
|
|
237
|
+
payload['estimate'] = null;
|
|
238
|
+
}
|
|
239
|
+
else if (options.estimate !== undefined) {
|
|
240
|
+
payload['estimate'] = options.estimate;
|
|
241
|
+
}
|
|
242
|
+
if (options.clearDueAt) {
|
|
243
|
+
payload['dueAt'] = null;
|
|
244
|
+
}
|
|
245
|
+
else if (options.dueAt !== undefined) {
|
|
246
|
+
payload['dueAt'] = options.dueAt;
|
|
247
|
+
}
|
|
248
|
+
if (Object.keys(payload).length === 0) {
|
|
249
|
+
throw new Error('At least one update field is required.');
|
|
250
|
+
}
|
|
251
|
+
return payload;
|
|
252
|
+
}
|
|
253
|
+
function buildUpdateContentPayload(options) {
|
|
254
|
+
const payload = {};
|
|
255
|
+
if (options.title !== undefined) {
|
|
256
|
+
payload['title'] = options.title;
|
|
257
|
+
}
|
|
258
|
+
if (options.contentMarkdown !== undefined) {
|
|
259
|
+
payload['contentMarkdown'] = options.contentMarkdown;
|
|
260
|
+
}
|
|
261
|
+
if (options.slug !== undefined) {
|
|
262
|
+
payload['slug'] = options.slug;
|
|
263
|
+
}
|
|
264
|
+
if (options.position !== undefined) {
|
|
265
|
+
payload['position'] = options.position;
|
|
266
|
+
}
|
|
267
|
+
if (options.baseRevision !== undefined) {
|
|
268
|
+
payload['baseRevision'] = options.baseRevision;
|
|
269
|
+
}
|
|
270
|
+
if (options.clearParent) {
|
|
271
|
+
payload['parentId'] = null;
|
|
272
|
+
}
|
|
273
|
+
else if (options.parentId !== undefined) {
|
|
274
|
+
payload['parentId'] = options.parentId;
|
|
275
|
+
}
|
|
276
|
+
if (Object.keys(payload).length === 0) {
|
|
277
|
+
throw new Error('At least one content update field is required.');
|
|
278
|
+
}
|
|
279
|
+
return payload;
|
|
280
|
+
}
|
|
281
|
+
function buildIssuesSearchInput(options) {
|
|
282
|
+
if (options.status !== undefined || options.statuses !== undefined) {
|
|
283
|
+
return options;
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
...options,
|
|
287
|
+
statuses: ["backlog" /* IssueStatusOption.Backlog */, "in_progress" /* IssueStatusOption.InProgress */, "blocked" /* IssueStatusOption.Blocked */]
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function buildAuthStatus(config, client) {
|
|
291
|
+
const target = client.describeTarget();
|
|
292
|
+
return {
|
|
293
|
+
profile: config.profile,
|
|
294
|
+
configPath: config.configPath,
|
|
295
|
+
configFileFound: config.configFileFound,
|
|
296
|
+
apiBaseUrl: config.apiBaseUrl ?? config_1.DEFAULT_API_BASE_URL,
|
|
297
|
+
isConfigured: client.isConfigured(),
|
|
298
|
+
hasAccessToken: target.hasAccessToken,
|
|
299
|
+
hasRefreshToken: target.hasRefreshToken,
|
|
300
|
+
organizationId: target.organizationId
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function buildDoctorReport(config, client) {
|
|
304
|
+
const auth = buildAuthStatus(config, client);
|
|
305
|
+
return {
|
|
306
|
+
command: 'doctor',
|
|
307
|
+
auth,
|
|
308
|
+
checks: {
|
|
309
|
+
apiBaseUrlConfigured: auth.isConfigured,
|
|
310
|
+
accessTokenConfigured: auth.hasAccessToken,
|
|
311
|
+
refreshTokenConfigured: auth.hasRefreshToken,
|
|
312
|
+
organizationConfigured: Boolean(auth.organizationId),
|
|
313
|
+
readyForAuthenticatedCommands: auth.isConfigured && auth.hasAccessToken && Boolean(auth.organizationId)
|
|
314
|
+
},
|
|
315
|
+
capabilities: {
|
|
316
|
+
issues: {
|
|
317
|
+
search: true,
|
|
318
|
+
list: true,
|
|
319
|
+
get: true,
|
|
320
|
+
create: true,
|
|
321
|
+
update: true,
|
|
322
|
+
delete: true,
|
|
323
|
+
comment: true,
|
|
324
|
+
transition: true,
|
|
325
|
+
link: true
|
|
326
|
+
},
|
|
327
|
+
projects: {
|
|
328
|
+
list: true
|
|
329
|
+
},
|
|
330
|
+
content: {
|
|
331
|
+
list: true,
|
|
332
|
+
get: true,
|
|
333
|
+
create: true,
|
|
334
|
+
update: true,
|
|
335
|
+
delete: true,
|
|
336
|
+
publish: false,
|
|
337
|
+
attach: false
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
notes: [
|
|
341
|
+
'Issue search, get, create, update, delete, comment, transition, and link are currently supported.',
|
|
342
|
+
'Project listing and content list, get, create, update, and delete are currently supported.',
|
|
343
|
+
'The current API surface does not expose a separate content publish endpoint.'
|
|
344
|
+
]
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function unsupportedContentError(subcommand) {
|
|
348
|
+
return new Error(`Content command "${subcommand}" is not yet supported by the installed Reximo CLI/API surface. Run \`reximo doctor --json\` to inspect current capabilities.`);
|
|
349
|
+
}
|
|
127
350
|
function createProgram(dependencies) {
|
|
128
351
|
const configOptions = {
|
|
129
352
|
env: dependencies.env,
|
|
@@ -180,6 +403,393 @@ function createProgram(dependencies) {
|
|
|
180
403
|
}
|
|
181
404
|
});
|
|
182
405
|
});
|
|
406
|
+
const authCommand = program.command('auth').description('Inspect Reximo authentication state.');
|
|
407
|
+
authCommand
|
|
408
|
+
.command('status')
|
|
409
|
+
.description('Show whether the current CLI profile is ready for authenticated API commands.')
|
|
410
|
+
.action(function action() {
|
|
411
|
+
const config = (0, config_1.loadResolvedConfig)(configOptions);
|
|
412
|
+
const client = dependencies.clientFactory(config);
|
|
413
|
+
const status = buildAuthStatus(config, client);
|
|
414
|
+
writeResult(this, dependencies, [
|
|
415
|
+
`Profile: ${status.profile}`,
|
|
416
|
+
`Config path: ${status.configPath}`,
|
|
417
|
+
`Config file found: ${status.configFileFound ? 'yes' : 'no'}`,
|
|
418
|
+
`API base URL: ${status.apiBaseUrl}`,
|
|
419
|
+
`Configured for API: ${status.isConfigured ? 'yes' : 'no'}`,
|
|
420
|
+
`Access token configured: ${status.hasAccessToken ? 'yes' : 'no'}`,
|
|
421
|
+
`Refresh token configured: ${status.hasRefreshToken ? 'yes' : 'no'}`,
|
|
422
|
+
`Organization: ${status.organizationId ?? '(not configured)'}`
|
|
423
|
+
], {
|
|
424
|
+
command: 'auth.status',
|
|
425
|
+
status
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
program
|
|
429
|
+
.command('doctor')
|
|
430
|
+
.description('Report Reximo CLI readiness and command-surface capabilities for automation.')
|
|
431
|
+
.action(function action() {
|
|
432
|
+
const config = (0, config_1.loadResolvedConfig)(configOptions);
|
|
433
|
+
const client = dependencies.clientFactory(config);
|
|
434
|
+
const report = buildDoctorReport(config, client);
|
|
435
|
+
writeResult(this, dependencies, [
|
|
436
|
+
`Profile: ${report.auth.profile}`,
|
|
437
|
+
`Configured for API: ${report.auth.isConfigured ? 'yes' : 'no'}`,
|
|
438
|
+
`Access token configured: ${report.auth.hasAccessToken ? 'yes' : 'no'}`,
|
|
439
|
+
`Refresh token configured: ${report.auth.hasRefreshToken ? 'yes' : 'no'}`,
|
|
440
|
+
`Organization: ${report.auth.organizationId ?? '(not configured)'}`,
|
|
441
|
+
`Ready for authenticated commands: ${report.auth.isConfigured && report.auth.hasAccessToken && Boolean(report.auth.organizationId) ? 'yes' : 'no'}`,
|
|
442
|
+
`Issue commands: search, list, get, create, update, delete, comment, transition, link`,
|
|
443
|
+
`Content commands: list, get, create, update, delete`,
|
|
444
|
+
`Unsupported content commands: publish, attach`
|
|
445
|
+
], report);
|
|
446
|
+
});
|
|
447
|
+
const issuesCommand = program.command('issues').description('Manage issues.');
|
|
448
|
+
issuesCommand
|
|
449
|
+
.command('search')
|
|
450
|
+
.alias('list')
|
|
451
|
+
.description('Search visible issues within the configured organization.')
|
|
452
|
+
.option('--page <page>', 'Page number (1-based)', Number)
|
|
453
|
+
.option('--page-size <pageSize>', 'Items per page', Number)
|
|
454
|
+
.option('--project-id <projectId>', 'Restrict to one project id', Number)
|
|
455
|
+
.option('--label-id <labelId>', 'Restrict to one label id', Number)
|
|
456
|
+
.option('--assignee-user-id <assigneeUserId>', 'Restrict to one assignee user id', Number)
|
|
457
|
+
.option('--watcher-user-id <watcherUserId>', 'Restrict to one watcher user id', Number)
|
|
458
|
+
.option('--activity-actor-user-id <activityActorUserId>', 'Restrict to issues with activity authored by one user', Number)
|
|
459
|
+
.option('--search <search>', 'Search by issue identifier, title, or description')
|
|
460
|
+
.option('--status <status>', 'Restrict to one status')
|
|
461
|
+
.option('--statuses <statuses>', 'Comma-separated list of statuses', parseCsvOption)
|
|
462
|
+
.option('--priority <priority>', 'Restrict to one priority')
|
|
463
|
+
.option('--sort-by <sortBy>', 'Sort field')
|
|
464
|
+
.option('--sort-order <sortOrder>', 'Sort direction')
|
|
465
|
+
.action(async function action(options) {
|
|
466
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
467
|
+
const result = await client.listIssues({
|
|
468
|
+
accessToken,
|
|
469
|
+
organizationId,
|
|
470
|
+
...buildIssuesSearchInput(options)
|
|
471
|
+
});
|
|
472
|
+
writeResult(this, dependencies, formatIssueLines(result), {
|
|
473
|
+
command: 'issues.search',
|
|
474
|
+
result
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
issuesCommand
|
|
478
|
+
.command('get <issueId>')
|
|
479
|
+
.alias('show')
|
|
480
|
+
.description('Get one issue by id in the configured organization.')
|
|
481
|
+
.action(async function action(issueId) {
|
|
482
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
483
|
+
const issue = await client.getIssue({
|
|
484
|
+
accessToken,
|
|
485
|
+
organizationId,
|
|
486
|
+
issueId
|
|
487
|
+
});
|
|
488
|
+
writeResult(this, dependencies, formatIssueDetailLines(issue), {
|
|
489
|
+
command: 'issues.get',
|
|
490
|
+
issue
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
issuesCommand
|
|
494
|
+
.command('create')
|
|
495
|
+
.description('Create one issue in the configured organization.')
|
|
496
|
+
.option('--title <title>', 'Issue title')
|
|
497
|
+
.option('--description-markdown <descriptionMarkdown>', 'Issue description in markdown')
|
|
498
|
+
.option('--status <status>', 'Issue status')
|
|
499
|
+
.option('--status-definition-id <statusDefinitionId>', 'Issue status definition id', Number)
|
|
500
|
+
.option('--priority <priority>', 'Issue priority')
|
|
501
|
+
.option('--project-id <projectId>', 'Project id', Number)
|
|
502
|
+
.option('--parent-issue-id <parentIssueId>', 'Parent issue id', Number)
|
|
503
|
+
.option('--position <position>', 'Issue position', Number)
|
|
504
|
+
.option('--estimate <estimate>', 'Issue estimate', Number)
|
|
505
|
+
.option('--due-at <dueAt>', 'Issue due date as an ISO-8601 string')
|
|
506
|
+
.action(async function action(options) {
|
|
507
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
508
|
+
if (!options.title?.trim()) {
|
|
509
|
+
throw new Error('Title is required');
|
|
510
|
+
}
|
|
511
|
+
const issue = await client.createIssue({
|
|
512
|
+
accessToken,
|
|
513
|
+
organizationId,
|
|
514
|
+
issue: {
|
|
515
|
+
...options,
|
|
516
|
+
title: options.title.trim()
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
writeResult(this, dependencies, formatIssueDetailLines(issue), {
|
|
520
|
+
command: 'issues.create',
|
|
521
|
+
issue
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
issuesCommand
|
|
525
|
+
.command('update <issueId>')
|
|
526
|
+
.description('Update one issue by id in the configured organization.')
|
|
527
|
+
.option('--title <title>', 'Issue title')
|
|
528
|
+
.option('--description-markdown <descriptionMarkdown>', 'Issue description in markdown')
|
|
529
|
+
.option('--status <status>', 'Issue status')
|
|
530
|
+
.option('--status-definition-id <statusDefinitionId>', 'Issue status definition id', Number)
|
|
531
|
+
.option('--priority <priority>', 'Issue priority')
|
|
532
|
+
.option('--parent-issue-id <parentIssueId>', 'Parent issue id', Number)
|
|
533
|
+
.option('--position <position>', 'Issue position', Number)
|
|
534
|
+
.option('--estimate <estimate>', 'Issue estimate', Number)
|
|
535
|
+
.option('--due-at <dueAt>', 'Issue due date as an ISO-8601 string')
|
|
536
|
+
.option('--clear-parent-issue', 'Clear the current parent issue')
|
|
537
|
+
.option('--clear-estimate', 'Clear the current estimate')
|
|
538
|
+
.option('--clear-due-at', 'Clear the current due date')
|
|
539
|
+
.option('--base-revision <baseRevision>', 'Optimistic concurrency token from the last issue read')
|
|
540
|
+
.action(async function action(issueId, options) {
|
|
541
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
542
|
+
const issue = await client.updateIssue({
|
|
543
|
+
accessToken,
|
|
544
|
+
organizationId,
|
|
545
|
+
issueId,
|
|
546
|
+
issue: buildUpdateIssuePayload(options)
|
|
547
|
+
});
|
|
548
|
+
writeResult(this, dependencies, formatIssueDetailLines(issue), {
|
|
549
|
+
command: 'issues.update',
|
|
550
|
+
issue
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
issuesCommand
|
|
554
|
+
.command('transition <issueId>')
|
|
555
|
+
.description('Transition one issue by updating its status or status definition.')
|
|
556
|
+
.option('--status <status>', 'Issue status')
|
|
557
|
+
.option('--status-definition-id <statusDefinitionId>', 'Issue status definition id', Number)
|
|
558
|
+
.option('--base-revision <baseRevision>', 'Optimistic concurrency token from the last issue read')
|
|
559
|
+
.action(async function action(issueId, options) {
|
|
560
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
561
|
+
if (options.status === undefined && options.statusDefinitionId === undefined) {
|
|
562
|
+
throw new Error('Either --status or --status-definition-id is required.');
|
|
563
|
+
}
|
|
564
|
+
const issue = await client.updateIssue({
|
|
565
|
+
accessToken,
|
|
566
|
+
organizationId,
|
|
567
|
+
issueId,
|
|
568
|
+
issue: {
|
|
569
|
+
...(options.status !== undefined ? { status: options.status } : {}),
|
|
570
|
+
...(options.statusDefinitionId !== undefined
|
|
571
|
+
? { statusDefinitionId: options.statusDefinitionId }
|
|
572
|
+
: {}),
|
|
573
|
+
...(options.baseRevision !== undefined ? { baseRevision: options.baseRevision } : {})
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
writeResult(this, dependencies, formatIssueDetailLines(issue), {
|
|
577
|
+
command: 'issues.transition',
|
|
578
|
+
issue
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
issuesCommand
|
|
582
|
+
.command('comment <issueId>')
|
|
583
|
+
.description('Create one issue comment in the configured organization.')
|
|
584
|
+
.option('--body-markdown <bodyMarkdown>', 'Issue comment body in markdown')
|
|
585
|
+
.option('--parent-comment-id <parentCommentId>', 'Optional parent comment id', Number)
|
|
586
|
+
.action(async function action(issueId, options) {
|
|
587
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
588
|
+
if (!options.bodyMarkdown?.trim()) {
|
|
589
|
+
throw new Error('Comment body is required');
|
|
590
|
+
}
|
|
591
|
+
const issue = await client.createIssueComment({
|
|
592
|
+
accessToken,
|
|
593
|
+
organizationId,
|
|
594
|
+
issueId,
|
|
595
|
+
comment: {
|
|
596
|
+
bodyMarkdown: options.bodyMarkdown.trim(),
|
|
597
|
+
...(options.parentCommentId !== undefined
|
|
598
|
+
? { parentCommentId: options.parentCommentId }
|
|
599
|
+
: {})
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
writeResult(this, dependencies, formatIssueDetailLines(issue), {
|
|
603
|
+
command: 'issues.comment',
|
|
604
|
+
issue
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
issuesCommand
|
|
608
|
+
.command('link <issueId>')
|
|
609
|
+
.description('Create one issue relation from the selected issue to another issue.')
|
|
610
|
+
.option('--target-issue-id <targetIssueId>', 'Target issue id', Number)
|
|
611
|
+
.option('--relation-type <relationType>', 'Relation type key')
|
|
612
|
+
.action(async function action(issueId, options) {
|
|
613
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
614
|
+
if (options.targetIssueId === undefined) {
|
|
615
|
+
throw new Error('Target issue id is required');
|
|
616
|
+
}
|
|
617
|
+
if (!options.relationType?.trim()) {
|
|
618
|
+
throw new Error('Relation type is required');
|
|
619
|
+
}
|
|
620
|
+
const issue = await client.createIssueRelation({
|
|
621
|
+
accessToken,
|
|
622
|
+
organizationId,
|
|
623
|
+
issueId,
|
|
624
|
+
relation: {
|
|
625
|
+
targetIssueId: options.targetIssueId,
|
|
626
|
+
relationType: options.relationType
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
writeResult(this, dependencies, formatIssueDetailLines(issue), {
|
|
630
|
+
command: 'issues.link',
|
|
631
|
+
issue
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
issuesCommand
|
|
635
|
+
.command('delete <issueId>')
|
|
636
|
+
.description('Delete one issue by id in the configured organization.')
|
|
637
|
+
.action(async function action(issueId) {
|
|
638
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
639
|
+
await client.deleteIssue({
|
|
640
|
+
accessToken,
|
|
641
|
+
organizationId,
|
|
642
|
+
issueId
|
|
643
|
+
});
|
|
644
|
+
writeResult(this, dependencies, [`Issue ${issueId} deleted.`], {
|
|
645
|
+
command: 'issues.delete',
|
|
646
|
+
issueId,
|
|
647
|
+
deleted: true
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
const projectsCommand = program.command('projects').description('Manage projects.');
|
|
651
|
+
projectsCommand
|
|
652
|
+
.command('list')
|
|
653
|
+
.description('List projects visible to the current user in the configured organization.')
|
|
654
|
+
.option('--page <page>', 'Page number (1-based)', Number)
|
|
655
|
+
.option('--page-size <pageSize>', 'Items per page', Number)
|
|
656
|
+
.option('--search <search>', 'Search by project name')
|
|
657
|
+
.option('--visibility <visibility>', 'Restrict to one visibility')
|
|
658
|
+
.option('--sort-by <sortBy>', 'Sort field')
|
|
659
|
+
.option('--sort-order <sortOrder>', 'Sort direction')
|
|
660
|
+
.action(async function action(options) {
|
|
661
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
662
|
+
const result = await client.listProjects({
|
|
663
|
+
accessToken,
|
|
664
|
+
organizationId,
|
|
665
|
+
...options
|
|
666
|
+
});
|
|
667
|
+
writeResult(this, dependencies, formatProjectLines(result), {
|
|
668
|
+
command: 'projects.list',
|
|
669
|
+
result
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
const contentCommand = program.command('content').description('Manage content entries.');
|
|
673
|
+
contentCommand
|
|
674
|
+
.command('list')
|
|
675
|
+
.description('List content entries visible to the current user in the configured organization.')
|
|
676
|
+
.option('--scope <scope>', 'Content scope filter')
|
|
677
|
+
.option('--project-id <projectId>', 'Project id filter', Number)
|
|
678
|
+
.option('--parent-id <parentId>', 'Parent content id filter', Number)
|
|
679
|
+
.option('--slug <slug>', 'Slug filter')
|
|
680
|
+
.action(async function action(options) {
|
|
681
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
682
|
+
const result = await client.listContent({
|
|
683
|
+
accessToken,
|
|
684
|
+
organizationId,
|
|
685
|
+
...options
|
|
686
|
+
});
|
|
687
|
+
writeResult(this, dependencies, formatContentLines(result), {
|
|
688
|
+
command: 'content.list',
|
|
689
|
+
result
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
contentCommand
|
|
693
|
+
.command('get <contentId>')
|
|
694
|
+
.alias('show')
|
|
695
|
+
.description('Get one content entry by id in the configured organization.')
|
|
696
|
+
.action(async function action(contentId) {
|
|
697
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
698
|
+
const content = await client.getContent({
|
|
699
|
+
accessToken,
|
|
700
|
+
organizationId,
|
|
701
|
+
contentId
|
|
702
|
+
});
|
|
703
|
+
writeResult(this, dependencies, formatContentDetailLines(content), {
|
|
704
|
+
command: 'content.get',
|
|
705
|
+
content
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
contentCommand
|
|
709
|
+
.command('create')
|
|
710
|
+
.description('Create one content entry in the configured organization.')
|
|
711
|
+
.option('--title <title>', 'Content title')
|
|
712
|
+
.option('--content-markdown <contentMarkdown>', 'Content body in markdown')
|
|
713
|
+
.option('--slug <slug>', 'Content slug')
|
|
714
|
+
.option('--project-id <projectId>', 'Project id', Number)
|
|
715
|
+
.option('--parent-id <parentId>', 'Parent content id', Number)
|
|
716
|
+
.option('--position <position>', 'Content position', Number)
|
|
717
|
+
.action(async function action(options) {
|
|
718
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
719
|
+
if (!options.title?.trim()) {
|
|
720
|
+
throw new Error('Title is required');
|
|
721
|
+
}
|
|
722
|
+
if (!options.contentMarkdown?.trim()) {
|
|
723
|
+
throw new Error('Content markdown is required');
|
|
724
|
+
}
|
|
725
|
+
const content = await client.createContent({
|
|
726
|
+
accessToken,
|
|
727
|
+
organizationId,
|
|
728
|
+
content: {
|
|
729
|
+
title: options.title.trim(),
|
|
730
|
+
contentMarkdown: options.contentMarkdown,
|
|
731
|
+
...(options.slug !== undefined ? { slug: options.slug } : {}),
|
|
732
|
+
...(options.projectId !== undefined ? { projectId: options.projectId } : {}),
|
|
733
|
+
...(options.parentId !== undefined ? { parentId: options.parentId } : {}),
|
|
734
|
+
...(options.position !== undefined ? { position: options.position } : {})
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
writeResult(this, dependencies, formatContentDetailLines(content), {
|
|
738
|
+
command: 'content.create',
|
|
739
|
+
content
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
contentCommand
|
|
743
|
+
.command('update <contentId>')
|
|
744
|
+
.description('Update one content entry by id in the configured organization.')
|
|
745
|
+
.option('--title <title>', 'Content title')
|
|
746
|
+
.option('--content-markdown <contentMarkdown>', 'Content body in markdown')
|
|
747
|
+
.option('--slug <slug>', 'Content slug')
|
|
748
|
+
.option('--parent-id <parentId>', 'Parent content id', Number)
|
|
749
|
+
.option('--position <position>', 'Content position', Number)
|
|
750
|
+
.option('--clear-parent', 'Clear the current parent content id')
|
|
751
|
+
.option('--base-revision <baseRevision>', 'Optimistic concurrency token from the last content read')
|
|
752
|
+
.action(async function action(contentId, options) {
|
|
753
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
754
|
+
const content = await client.updateContent({
|
|
755
|
+
accessToken,
|
|
756
|
+
organizationId,
|
|
757
|
+
contentId,
|
|
758
|
+
content: buildUpdateContentPayload(options)
|
|
759
|
+
});
|
|
760
|
+
writeResult(this, dependencies, formatContentDetailLines(content), {
|
|
761
|
+
command: 'content.update',
|
|
762
|
+
content
|
|
763
|
+
});
|
|
764
|
+
});
|
|
765
|
+
contentCommand
|
|
766
|
+
.command('delete <contentId>')
|
|
767
|
+
.description('Delete one content entry by id in the configured organization.')
|
|
768
|
+
.action(async function action(contentId) {
|
|
769
|
+
const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
|
|
770
|
+
await client.deleteContent({
|
|
771
|
+
accessToken,
|
|
772
|
+
organizationId,
|
|
773
|
+
contentId
|
|
774
|
+
});
|
|
775
|
+
writeResult(this, dependencies, [`Content ${contentId} deleted.`], {
|
|
776
|
+
command: 'content.delete',
|
|
777
|
+
contentId,
|
|
778
|
+
deleted: true
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
contentCommand
|
|
782
|
+
.command('publish <contentId>')
|
|
783
|
+
.description('Publish one content artifact when supported by the installed CLI/API surface.')
|
|
784
|
+
.action(async () => {
|
|
785
|
+
throw unsupportedContentError("publish" /* UnsupportedContentCommand.Publish */);
|
|
786
|
+
});
|
|
787
|
+
contentCommand
|
|
788
|
+
.command('attach <contentId>')
|
|
789
|
+
.description('Attach an artifact to content when supported by the installed CLI/API surface.')
|
|
790
|
+
.action(async () => {
|
|
791
|
+
throw unsupportedContentError("attach" /* UnsupportedContentCommand.Attach */);
|
|
792
|
+
});
|
|
183
793
|
const configCommand = program
|
|
184
794
|
.command('config')
|
|
185
795
|
.description('Inspect resolved CLI configuration.');
|