jira-ai 0.4.10 → 0.4.12
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.
|
@@ -1,37 +1,16 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
2
|
+
import { validateIssuePermissions } from '../lib/jira-client.js';
|
|
3
3
|
import { formatTaskDetails } from '../lib/formatters.js';
|
|
4
4
|
import { CommandError } from '../lib/errors.js';
|
|
5
5
|
import { ui } from '../lib/ui.js';
|
|
6
|
-
import { isCommandAllowed, validateIssueAgainstFilters } from '../lib/settings.js';
|
|
7
6
|
export async function taskWithDetailsCommand(taskId, options = {}) {
|
|
8
7
|
ui.startSpinner(`Fetching details for ${taskId}...`);
|
|
9
8
|
try {
|
|
10
|
-
const task = await
|
|
9
|
+
const task = await validateIssuePermissions(taskId, 'task-with-details', {
|
|
11
10
|
includeHistory: options.includeDetailedHistory,
|
|
12
11
|
historyLimit: options.historyLimit ? parseInt(options.historyLimit, 10) : undefined,
|
|
13
12
|
historyOffset: options.historyOffset ? parseInt(options.historyOffset, 10) : undefined,
|
|
14
13
|
});
|
|
15
|
-
const projectKey = task.key.split('-')[0];
|
|
16
|
-
// Check if command is allowed for this project
|
|
17
|
-
if (!isCommandAllowed('task-with-details', projectKey)) {
|
|
18
|
-
ui.failSpinner(chalk.red('Command not allowed for this project'));
|
|
19
|
-
throw new CommandError(`Command 'task-with-details' is not allowed for project ${projectKey}.`, {
|
|
20
|
-
hints: [
|
|
21
|
-
`Update settings.yaml to enable this command for this project.`
|
|
22
|
-
]
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
// Check granular filters
|
|
26
|
-
const currentUser = await getCurrentUser();
|
|
27
|
-
if (!validateIssueAgainstFilters(task, currentUser.accountId)) {
|
|
28
|
-
ui.failSpinner(chalk.red('Access denied by project filters'));
|
|
29
|
-
throw new CommandError(`Access to issue ${taskId} is restricted by project filters.`, {
|
|
30
|
-
hints: [
|
|
31
|
-
`This project has filters that you do not meet (e.g., participated roles).`
|
|
32
|
-
]
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
14
|
ui.succeedSpinner(chalk.green('Task details retrieved'));
|
|
36
15
|
console.log(formatTaskDetails(task));
|
|
37
16
|
}
|
package/dist/lib/jira-client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Version3Client } from 'jira.js';
|
|
2
2
|
import { calculateStatusStatistics, convertADFToMarkdown } from './utils.js';
|
|
3
3
|
import { loadCredentials } from './auth-storage.js';
|
|
4
|
-
import { applyGlobalFilters, isProjectAllowed, isCommandAllowed, validateIssueAgainstFilters } from './settings.js';
|
|
4
|
+
import { applyGlobalFilters, isProjectAllowed, isCommandAllowed, validateIssueAgainstFilters, loadSettings } from './settings.js';
|
|
5
5
|
import { CommandError } from './errors.js';
|
|
6
6
|
let jiraClient = null;
|
|
7
7
|
let organizationOverride = undefined;
|
|
@@ -356,8 +356,8 @@ export async function createIssue(projectKey, summary, issueTypeName, parentKey)
|
|
|
356
356
|
/**
|
|
357
357
|
* Validate that the current user has permission to perform a command on an issue
|
|
358
358
|
*/
|
|
359
|
-
export async function validateIssuePermissions(issueKey, commandName) {
|
|
360
|
-
const task = await getTaskWithDetails(issueKey);
|
|
359
|
+
export async function validateIssuePermissions(issueKey, commandName, options = {}) {
|
|
360
|
+
const task = await getTaskWithDetails(issueKey, options);
|
|
361
361
|
const projectKey = task.key.split('-')[0];
|
|
362
362
|
if (!isProjectAllowed(projectKey)) {
|
|
363
363
|
throw new CommandError(`Project '${projectKey}' is not allowed by your settings.`);
|
|
@@ -373,6 +373,28 @@ export async function validateIssuePermissions(issueKey, commandName) {
|
|
|
373
373
|
hints: [`This project has filters that you do not meet (e.g., participated roles).`]
|
|
374
374
|
});
|
|
375
375
|
}
|
|
376
|
+
// Check JQL filters
|
|
377
|
+
const settings = loadSettings();
|
|
378
|
+
let project = settings.projects.find(p => typeof p !== 'string' && p.key === projectKey);
|
|
379
|
+
if (!project) {
|
|
380
|
+
project = settings.projects.find(p => typeof p === 'string' && (p === 'all' || p === projectKey));
|
|
381
|
+
}
|
|
382
|
+
if (project && typeof project !== 'string' && project.filters?.jql) {
|
|
383
|
+
const client = getJiraClient();
|
|
384
|
+
const jql = `key = "${issueKey}" AND (${project.filters.jql})`;
|
|
385
|
+
const response = await client.issueSearch.searchForIssuesUsingJqlEnhancedSearch({
|
|
386
|
+
jql,
|
|
387
|
+
maxResults: 1,
|
|
388
|
+
fields: ['key'],
|
|
389
|
+
});
|
|
390
|
+
if (!response.issues || response.issues.length === 0) {
|
|
391
|
+
throw new CommandError(`Access to issue ${issueKey} is restricted by project filters.`, {
|
|
392
|
+
hints: [
|
|
393
|
+
`This project has a JQL filter that this issue does not meet: ${project.filters.jql}`,
|
|
394
|
+
]
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
376
398
|
return task;
|
|
377
399
|
}
|
|
378
400
|
/**
|
package/dist/lib/settings.js
CHANGED
|
@@ -32,7 +32,7 @@ export function loadSettings() {
|
|
|
32
32
|
console.error('Error migrating settings.yaml:', error);
|
|
33
33
|
const defaultSettings = {
|
|
34
34
|
projects: ['all'],
|
|
35
|
-
commands: ['me', 'projects', 'task-with-details', 'run-jql', 'list-issue-types', 'project-statuses', 'create-task', 'list-colleagues', 'add-comment', 'add-label', 'delete-label', 'get-issue-statistics', 'get-person-worklog', 'organization', 'transition', 'update-description']
|
|
35
|
+
commands: ['me', 'projects', 'task-with-details', 'run-jql', 'list-issue-types', 'project-statuses', 'create-task', 'list-colleagues', 'add-comment', 'add-label-to-issue', 'delete-label-from-issue', 'get-issue-statistics', 'get-person-worklog', 'organization', 'transition', 'update-description']
|
|
36
36
|
};
|
|
37
37
|
cachedSettings = defaultSettings;
|
|
38
38
|
return cachedSettings;
|
|
@@ -42,7 +42,7 @@ export function loadSettings() {
|
|
|
42
42
|
// Create default settings.yaml if it doesn't exist anywhere
|
|
43
43
|
const defaultSettings = {
|
|
44
44
|
projects: ['all'],
|
|
45
|
-
commands: ['me', 'projects', 'task-with-details', 'run-jql', 'list-issue-types', 'project-statuses', 'create-task', 'list-colleagues', 'add-comment', 'add-label', 'delete-label', 'get-issue-statistics', 'get-person-worklog', 'organization', 'transition', 'update-description']
|
|
45
|
+
commands: ['me', 'projects', 'task-with-details', 'run-jql', 'list-issue-types', 'project-statuses', 'create-task', 'list-colleagues', 'add-comment', 'add-label-to-issue', 'delete-label-from-issue', 'get-issue-statistics', 'get-person-worklog', 'organization', 'transition', 'update-description']
|
|
46
46
|
};
|
|
47
47
|
try {
|
|
48
48
|
const yamlStr = yaml.dump(defaultSettings);
|
|
@@ -68,7 +68,7 @@ export function loadSettings() {
|
|
|
68
68
|
const settings = rawSettings;
|
|
69
69
|
cachedSettings = {
|
|
70
70
|
projects: settings?.projects || ['all'],
|
|
71
|
-
commands: settings?.commands || ['me', 'projects', 'task-with-details', 'run-jql', 'list-issue-types', 'project-statuses', 'create-task', 'list-colleagues', 'add-comment', 'add-label', 'delete-label', 'get-issue-statistics', 'get-person-worklog', 'organization', 'transition', 'update-description']
|
|
71
|
+
commands: settings?.commands || ['me', 'projects', 'task-with-details', 'run-jql', 'list-issue-types', 'project-statuses', 'create-task', 'list-colleagues', 'add-comment', 'add-label-to-issue', 'delete-label-from-issue', 'get-issue-statistics', 'get-person-worklog', 'organization', 'transition', 'update-description']
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
else {
|
|
@@ -111,7 +111,10 @@ export function isCommandAllowed(commandName, projectKey) {
|
|
|
111
111
|
return true;
|
|
112
112
|
}
|
|
113
113
|
if (projectKey) {
|
|
114
|
-
|
|
114
|
+
let project = settings.projects.find(p => typeof p !== 'string' && p.key === projectKey);
|
|
115
|
+
if (!project) {
|
|
116
|
+
project = settings.projects.find(p => typeof p === 'string' && (p === 'all' || p === projectKey));
|
|
117
|
+
}
|
|
115
118
|
if (project && typeof project !== 'string' && project.commands) {
|
|
116
119
|
return project.commands.includes(commandName);
|
|
117
120
|
}
|
|
@@ -166,11 +169,12 @@ export function applyGlobalFilters(jql) {
|
|
|
166
169
|
export function validateIssueAgainstFilters(issue, currentUserId) {
|
|
167
170
|
const settings = loadSettings();
|
|
168
171
|
const projectKey = issue.key.split('-')[0];
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
// Find specific project config first
|
|
173
|
+
let project = settings.projects.find(p => typeof p !== 'string' && p.key === projectKey);
|
|
174
|
+
// If not found, look for string match (exact project key or 'all')
|
|
175
|
+
if (!project) {
|
|
176
|
+
project = settings.projects.find(p => typeof p === 'string' && (p === 'all' || p === projectKey));
|
|
177
|
+
}
|
|
174
178
|
if (!project) {
|
|
175
179
|
return false;
|
|
176
180
|
}
|
package/dist/lib/validation.js
CHANGED
|
@@ -92,5 +92,5 @@ export const ProjectSettingSchema = z.union([
|
|
|
92
92
|
]);
|
|
93
93
|
export const SettingsSchema = z.object({
|
|
94
94
|
projects: z.array(ProjectSettingSchema).nullish().transform(val => val || ['all']),
|
|
95
|
-
commands: z.array(z.string()).nullish().transform(val => val || ['me', 'projects', 'task-with-details', 'run-jql', 'list-issue-types', 'project-statuses', 'create-task', 'list-colleagues', 'add-comment', 'add-label', 'delete-label', 'get-issue-statistics', 'get-person-worklog', 'organization', 'transition', 'update-description']),
|
|
95
|
+
commands: z.array(z.string()).nullish().transform(val => val || ['me', 'projects', 'task-with-details', 'run-jql', 'list-issue-types', 'project-statuses', 'create-task', 'list-colleagues', 'add-comment', 'add-label-to-issue', 'delete-label-from-issue', 'get-issue-statistics', 'get-person-worklog', 'organization', 'transition', 'update-description']),
|
|
96
96
|
});
|