jira-ai 0.6.5 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +11 -2
- package/dist/commands/confluence.js +90 -1
- package/dist/lib/confluence-client.js +43 -0
- package/dist/lib/validation.js +3 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ import { createTaskCommand } from './commands/create-task.js';
|
|
|
18
18
|
import { transitionCommand } from './commands/transition.js';
|
|
19
19
|
import { getIssueStatisticsCommand } from './commands/get-issue-statistics.js';
|
|
20
20
|
import { getPersonWorklogCommand } from './commands/get-person-worklog.js';
|
|
21
|
-
import { confluenceGetPageCommand, confluenceListSpacesCommand, confluenceGetSpacePagesHierarchyCommand } from './commands/confluence.js';
|
|
21
|
+
import { confluenceGetPageCommand, confluenceListSpacesCommand, confluenceGetSpacePagesHierarchyCommand, confluenceAddCommentCommand, confluenceCreatePageCommand } from './commands/confluence.js';
|
|
22
22
|
import { aboutCommand } from './commands/about.js';
|
|
23
23
|
import { authCommand } from './commands/auth.js';
|
|
24
24
|
import { settingsCommand } from './commands/settings.js';
|
|
@@ -30,7 +30,7 @@ import { checkForUpdate, formatUpdateMessage, checkForUpdateSync } from './lib/u
|
|
|
30
30
|
import { CliError } from './types/errors.js';
|
|
31
31
|
import { CommandError } from './lib/errors.js';
|
|
32
32
|
import { ui } from './lib/ui.js';
|
|
33
|
-
import { CreateTaskSchema, AddCommentSchema, UpdateDescriptionSchema, RunJqlSchema, GetPersonWorklogSchema, GetIssueStatisticsSchema, validateOptions, IssueKeySchema, ProjectKeySchema, TimeframeSchema } from './lib/validation.js';
|
|
33
|
+
import { CreateTaskSchema, AddCommentSchema, UpdateDescriptionSchema, ConfluenceAddCommentSchema, RunJqlSchema, GetPersonWorklogSchema, GetIssueStatisticsSchema, validateOptions, IssueKeySchema, ProjectKeySchema, TimeframeSchema } from './lib/validation.js';
|
|
34
34
|
import { realpathSync } from 'fs';
|
|
35
35
|
// Load environment variables
|
|
36
36
|
// @ts-ignore - quiet option exists but is not in types
|
|
@@ -263,6 +263,15 @@ confl
|
|
|
263
263
|
.command('pages <space-key>')
|
|
264
264
|
.description('Display a hierarchical tree view of pages within a specific space.')
|
|
265
265
|
.action(withPermission('confl', confluenceGetSpacePagesHierarchyCommand, { skipValidation: false }));
|
|
266
|
+
confl
|
|
267
|
+
.command('create-page <space> <title> [parent-page]')
|
|
268
|
+
.description('Create a new Confluence page')
|
|
269
|
+
.action(withPermission('confl', confluenceCreatePageCommand, { skipValidation: false }));
|
|
270
|
+
confl
|
|
271
|
+
.command('add-comment <url>')
|
|
272
|
+
.description('Add a new comment to a Confluence page using content from a local Markdown file.')
|
|
273
|
+
.requiredOption('--from-file <path>', 'Path to Markdown file')
|
|
274
|
+
.action(withPermission('confl', confluenceAddCommentCommand, { schema: ConfluenceAddCommentSchema }));
|
|
266
275
|
// About command (always allowed)
|
|
267
276
|
program
|
|
268
277
|
.command('about')
|
|
@@ -1,9 +1,98 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { markdownToAdf } from 'marklassian';
|
|
5
|
+
import { getPage, getPageComments, parseConfluenceUrl, listSpaces, getSpacePagesHierarchy, addPageComment, createPage } from '../lib/confluence-client.js';
|
|
3
6
|
import { formatConfluencePage, formatConfluenceSpaces, formatConfluencePageHierarchy } from '../lib/formatters.js';
|
|
4
7
|
import { ui } from '../lib/ui.js';
|
|
5
8
|
import { CommandError } from '../lib/errors.js';
|
|
6
9
|
import { isConfluenceSpaceAllowed } from '../lib/settings.js';
|
|
10
|
+
export async function confluenceCreatePageCommand(space, title, parentPage) {
|
|
11
|
+
// Validate space key
|
|
12
|
+
if (!isConfluenceSpaceAllowed(space)) {
|
|
13
|
+
throw new CommandError(`Access to Confluence space '${space}' is restricted by your settings.`);
|
|
14
|
+
}
|
|
15
|
+
ui.startSpinner(`Creating Confluence page '${title}' in space '${space}'...`);
|
|
16
|
+
try {
|
|
17
|
+
const url = await createPage(space, title, parentPage);
|
|
18
|
+
ui.succeedSpinner(chalk.green('Confluence page created successfully'));
|
|
19
|
+
console.log(chalk.cyan(`\nURL: ${url}`));
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
ui.failSpinner();
|
|
23
|
+
if (error instanceof CommandError)
|
|
24
|
+
throw error;
|
|
25
|
+
const errorMsg = error.message?.toLowerCase() || '';
|
|
26
|
+
const hints = [];
|
|
27
|
+
if (errorMsg.includes('401') || errorMsg.includes('403') || errorMsg.includes('unauthorized')) {
|
|
28
|
+
hints.push('Authentication failed or you do not have permission to create pages in this space.');
|
|
29
|
+
}
|
|
30
|
+
else if (errorMsg.includes('404')) {
|
|
31
|
+
hints.push('Space or parent page not found.');
|
|
32
|
+
}
|
|
33
|
+
throw new CommandError(`Failed to create Confluence page: ${error.message}`, { hints });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function confluenceAddCommentCommand(url, options) {
|
|
37
|
+
const { fromFile } = options;
|
|
38
|
+
// Validate space key before proceeding
|
|
39
|
+
try {
|
|
40
|
+
const { spaceKey } = parseConfluenceUrl(url);
|
|
41
|
+
if (spaceKey && !isConfluenceSpaceAllowed(spaceKey)) {
|
|
42
|
+
throw new CommandError(`Access to Confluence space '${spaceKey}' is restricted by your settings.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
if (e instanceof CommandError)
|
|
47
|
+
throw e;
|
|
48
|
+
// URL parsing errors will be caught during addPageComment
|
|
49
|
+
}
|
|
50
|
+
// Resolve and read file
|
|
51
|
+
const absolutePath = path.resolve(fromFile);
|
|
52
|
+
let markdownContent;
|
|
53
|
+
try {
|
|
54
|
+
markdownContent = fs.readFileSync(absolutePath, 'utf-8');
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw new CommandError(`Error reading file: ${error.message}`, {
|
|
58
|
+
hints: ['Make sure the file exists and you have permission to read it.']
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (markdownContent.trim() === '') {
|
|
62
|
+
throw new CommandError('Markdown file is empty');
|
|
63
|
+
}
|
|
64
|
+
// Convert Markdown to ADF
|
|
65
|
+
let adfContent;
|
|
66
|
+
try {
|
|
67
|
+
adfContent = markdownToAdf(markdownContent);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
throw new CommandError(`Error converting Markdown to ADF: ${error.message}`, {
|
|
71
|
+
hints: ['Ensure the Markdown content is valid.']
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
ui.startSpinner('Adding comment to Confluence page...');
|
|
75
|
+
try {
|
|
76
|
+
await addPageComment(url, adfContent);
|
|
77
|
+
ui.succeedSpinner(chalk.green('Comment added successfully to Confluence page'));
|
|
78
|
+
console.log(chalk.gray(`\nPage: ${url}`));
|
|
79
|
+
console.log(chalk.gray(`File: ${absolutePath}`));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
ui.failSpinner();
|
|
83
|
+
if (error instanceof CommandError)
|
|
84
|
+
throw error;
|
|
85
|
+
const errorMsg = error.message?.toLowerCase() || '';
|
|
86
|
+
const hints = [];
|
|
87
|
+
if (errorMsg.includes('404') || errorMsg.includes('not found')) {
|
|
88
|
+
hints.push('The Confluence page was not found. Check if the URL is correct.');
|
|
89
|
+
}
|
|
90
|
+
else if (errorMsg.includes('401') || errorMsg.includes('403') || errorMsg.includes('unauthorized')) {
|
|
91
|
+
hints.push('Authentication failed or you do not have permission to comment on this page.');
|
|
92
|
+
}
|
|
93
|
+
throw new CommandError(`Failed to add comment: ${error.message}`, { hints });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
7
96
|
export async function confluenceGetPageCommand(url) {
|
|
8
97
|
// Check permission before fetching if space key can be extracted from URL
|
|
9
98
|
try {
|
|
@@ -182,3 +182,46 @@ export async function getSpacePagesHierarchy(spaceKey, maxDepth = 5) {
|
|
|
182
182
|
}
|
|
183
183
|
return hierarchy;
|
|
184
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Add a comment to a Confluence page
|
|
187
|
+
*/
|
|
188
|
+
export async function addPageComment(url, adfContent) {
|
|
189
|
+
const client = getConfluenceClient();
|
|
190
|
+
const { pageId } = parseConfluenceUrl(url);
|
|
191
|
+
// @ts-ignore - CreateContent type requires title and space which are not needed for comments
|
|
192
|
+
await client.content.createContent({
|
|
193
|
+
type: 'comment',
|
|
194
|
+
container: { id: pageId, type: 'page' },
|
|
195
|
+
body: {
|
|
196
|
+
atlas_doc_format: {
|
|
197
|
+
value: JSON.stringify(adfContent),
|
|
198
|
+
representation: 'atlas_doc_format'
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Create a new Confluence page
|
|
205
|
+
*/
|
|
206
|
+
export async function createPage(spaceKey, title, parentId) {
|
|
207
|
+
const client = getConfluenceClient();
|
|
208
|
+
const response = await client.content.createContent({
|
|
209
|
+
type: 'page',
|
|
210
|
+
title,
|
|
211
|
+
space: { key: spaceKey },
|
|
212
|
+
ancestors: parentId ? [{ id: parentId }] : undefined,
|
|
213
|
+
body: {
|
|
214
|
+
atlas_doc_format: {
|
|
215
|
+
value: JSON.stringify({
|
|
216
|
+
type: 'doc',
|
|
217
|
+
version: 1,
|
|
218
|
+
content: []
|
|
219
|
+
}),
|
|
220
|
+
representation: 'atlas_doc_format'
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
// @ts-ignore - accessing host to construct URL
|
|
225
|
+
const host = client.config.host || '';
|
|
226
|
+
return `${host.replace(/\/$/, '')}/pages/${response.id}`;
|
|
227
|
+
}
|
package/dist/lib/validation.js
CHANGED
|
@@ -62,6 +62,9 @@ export const AddCommentSchema = z.object({
|
|
|
62
62
|
filePath: z.string().trim().min(1, 'File path is required').pipe(FilePathSchema),
|
|
63
63
|
issueKey: z.string().trim().min(1, 'Issue key is required').pipe(IssueKeySchema),
|
|
64
64
|
});
|
|
65
|
+
export const ConfluenceAddCommentSchema = z.object({
|
|
66
|
+
fromFile: z.string().trim().min(1, 'File path is required').pipe(FilePathSchema),
|
|
67
|
+
});
|
|
65
68
|
export const UpdateDescriptionSchema = z.object({
|
|
66
69
|
fromFile: z.string().trim().min(1, 'File path is required').pipe(FilePathSchema),
|
|
67
70
|
});
|