jira-ai 0.6.3 → 0.6.4
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/commands/confluence.js +18 -1
- package/dist/commands/settings.js +21 -11
- package/dist/lib/auth-storage.js +15 -1
- package/dist/lib/confluence-client.js +13 -11
- package/dist/lib/formatters.js +27 -10
- package/dist/lib/jira-client.js +10 -10
- package/dist/lib/settings.js +81 -39
- package/dist/lib/validation.js +10 -2
- package/package.json +1 -1
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { getPage, getPageComments } from '../lib/confluence-client.js';
|
|
2
|
+
import { getPage, getPageComments, parseConfluenceUrl } from '../lib/confluence-client.js';
|
|
3
3
|
import { formatConfluencePage } from '../lib/formatters.js';
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
5
|
import { CommandError } from '../lib/errors.js';
|
|
6
|
+
import { isConfluenceSpaceAllowed } from '../lib/settings.js';
|
|
6
7
|
export async function confluenceGetPageCommand(url) {
|
|
8
|
+
// Check permission before fetching if space key can be extracted from URL
|
|
9
|
+
try {
|
|
10
|
+
const { spaceKey } = parseConfluenceUrl(url);
|
|
11
|
+
if (spaceKey && !isConfluenceSpaceAllowed(spaceKey)) {
|
|
12
|
+
throw new CommandError(`Access to Confluence space '${spaceKey}' is restricted by your settings.`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
if (e instanceof CommandError)
|
|
17
|
+
throw e;
|
|
18
|
+
// If URL parsing fails, let the fetch attempt handle it or catch it later
|
|
19
|
+
}
|
|
7
20
|
ui.startSpinner(`Fetching Confluence page details for: ${url}`);
|
|
8
21
|
try {
|
|
9
22
|
const [page, comments] = await Promise.all([
|
|
10
23
|
getPage(url),
|
|
11
24
|
getPageComments(url)
|
|
12
25
|
]);
|
|
26
|
+
// Double check with the actual space name/key from the fetched page
|
|
27
|
+
if (!isConfluenceSpaceAllowed(page.space)) {
|
|
28
|
+
throw new CommandError(`Access to Confluence space '${page.space}' is restricted by your settings.`);
|
|
29
|
+
}
|
|
13
30
|
ui.succeedSpinner(chalk.green('Confluence page details retrieved'));
|
|
14
31
|
console.log(formatConfluencePage(page, comments));
|
|
15
32
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import yaml from 'js-yaml';
|
|
4
|
-
import { loadSettings, saveSettings, DEFAULT_SETTINGS } from '../lib/settings.js';
|
|
4
|
+
import { loadSettings, saveSettings, DEFAULT_SETTINGS, migrateSettings } from '../lib/settings.js';
|
|
5
5
|
import { formatSettings } from '../lib/formatters.js';
|
|
6
6
|
import { ui } from '../lib/ui.js';
|
|
7
7
|
import { SettingsSchema } from '../lib/validation.js';
|
|
@@ -57,23 +57,33 @@ async function validateSettingsFile(filePath) {
|
|
|
57
57
|
.join('\n');
|
|
58
58
|
throw new CommandError(`Invalid settings structure:\n${messages}`);
|
|
59
59
|
}
|
|
60
|
-
const settings = result.data;
|
|
60
|
+
const settings = migrateSettings(result.data);
|
|
61
61
|
// Deep Validation
|
|
62
62
|
ui.updateSpinner('Performing deep validation against Jira...');
|
|
63
63
|
try {
|
|
64
64
|
validateEnvVars();
|
|
65
65
|
const projects = await getProjects();
|
|
66
66
|
const projectKeys = new Set(projects.map(p => p.key));
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
const validateOrg = (orgSettings, label) => {
|
|
68
|
+
const projectsToValidate = orgSettings['allowed-jira-projects'] || [];
|
|
69
|
+
for (const p of projectsToValidate) {
|
|
70
|
+
const key = typeof p === 'string' ? p : p.key;
|
|
71
|
+
if (key === 'all')
|
|
72
|
+
continue;
|
|
73
|
+
if (!projectKeys.has(key)) {
|
|
74
|
+
const msg = `Project "${key}" (in ${label}) not found in Jira.`;
|
|
75
|
+
ui.failSpinner(`Deep validation failed: ${msg}`);
|
|
76
|
+
throw new CommandError(msg);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
if (settings.defaults) {
|
|
81
|
+
validateOrg(settings.defaults, 'defaults');
|
|
82
|
+
}
|
|
83
|
+
if (settings.organizations) {
|
|
84
|
+
for (const [alias, orgSettings] of Object.entries(settings.organizations)) {
|
|
85
|
+
validateOrg(orgSettings, `organizations.${alias}`);
|
|
74
86
|
}
|
|
75
|
-
// If project has specific commands, we could validate them too,
|
|
76
|
-
// but they are just strings matched against command names.
|
|
77
87
|
}
|
|
78
88
|
ui.succeedSpinner(chalk.green('Settings are valid!'));
|
|
79
89
|
return settings;
|
package/dist/lib/auth-storage.js
CHANGED
|
@@ -137,10 +137,24 @@ export function getOrganizations() {
|
|
|
137
137
|
const config = loadConfig();
|
|
138
138
|
return config.organizations;
|
|
139
139
|
}
|
|
140
|
+
let organizationOverride = undefined;
|
|
140
141
|
/**
|
|
141
|
-
|
|
142
|
+
|
|
143
|
+
* Set a global organization override for the current execution
|
|
144
|
+
|
|
145
|
+
*/
|
|
146
|
+
export function setOrganizationOverride(alias) {
|
|
147
|
+
organizationOverride = alias;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
|
|
151
|
+
* Get the currently active organization alias, respecting override
|
|
152
|
+
|
|
142
153
|
*/
|
|
143
154
|
export function getCurrentOrganizationAlias() {
|
|
155
|
+
if (organizationOverride) {
|
|
156
|
+
return organizationOverride;
|
|
157
|
+
}
|
|
144
158
|
const config = loadConfig();
|
|
145
159
|
return config.current;
|
|
146
160
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { ConfluenceClient } from 'confluence.js';
|
|
2
|
-
import { loadCredentials } from './auth-storage.js';
|
|
2
|
+
import { loadCredentials, getCurrentOrganizationAlias, setOrganizationOverride as setAuthOrgOverride } from './auth-storage.js';
|
|
3
3
|
import { convertADFToMarkdown } from './utils.js';
|
|
4
4
|
let confluenceClient = null;
|
|
5
|
-
let organizationOverride = undefined;
|
|
6
5
|
/**
|
|
7
6
|
* Set a global organization override for the current execution
|
|
8
7
|
*/
|
|
9
8
|
export function setOrganizationOverride(alias) {
|
|
10
|
-
|
|
9
|
+
setAuthOrgOverride(alias);
|
|
11
10
|
confluenceClient = null; // Force client recreation
|
|
12
11
|
}
|
|
13
12
|
/**
|
|
@@ -30,7 +29,8 @@ export function getConfluenceClient() {
|
|
|
30
29
|
});
|
|
31
30
|
}
|
|
32
31
|
else {
|
|
33
|
-
const
|
|
32
|
+
const alias = getCurrentOrganizationAlias();
|
|
33
|
+
const storedCreds = loadCredentials(alias);
|
|
34
34
|
if (storedCreds) {
|
|
35
35
|
confluenceClient = new ConfluenceClient({
|
|
36
36
|
host: storedCreds.host.replace(/\/$/, ''),
|
|
@@ -43,8 +43,8 @@ export function getConfluenceClient() {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
else {
|
|
46
|
-
const errorMsg =
|
|
47
|
-
? `Credentials for organization "${
|
|
46
|
+
const errorMsg = alias
|
|
47
|
+
? `Credentials for organization "${alias}" not found.`
|
|
48
48
|
: 'Credentials not found. Please set environment variables or run "jira-ai auth"';
|
|
49
49
|
throw new Error(errorMsg);
|
|
50
50
|
}
|
|
@@ -53,20 +53,22 @@ export function getConfluenceClient() {
|
|
|
53
53
|
return confluenceClient;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Parse Confluence URL to extract page ID
|
|
56
|
+
* Parse Confluence URL to extract page ID and space key
|
|
57
57
|
*/
|
|
58
58
|
export function parseConfluenceUrl(url) {
|
|
59
59
|
try {
|
|
60
60
|
const parsedUrl = new URL(url);
|
|
61
61
|
// Pattern: /wiki/spaces/SPACE/pages/PAGE_ID/TITLE
|
|
62
|
+
const spaceMatch = parsedUrl.pathname.match(/\/spaces\/([^/]+)/);
|
|
63
|
+
const spaceKey = spaceMatch ? spaceMatch[1] : undefined;
|
|
62
64
|
const pagesMatch = parsedUrl.pathname.match(/\/pages\/(\d+)/);
|
|
63
65
|
if (pagesMatch && pagesMatch[1]) {
|
|
64
|
-
return pagesMatch[1];
|
|
66
|
+
return { pageId: pagesMatch[1], spaceKey };
|
|
65
67
|
}
|
|
66
68
|
// Pattern: /wiki/pages/viewpage.action?pageId=PAGE_ID
|
|
67
69
|
const pageIdParam = parsedUrl.searchParams.get('pageId');
|
|
68
70
|
if (pageIdParam) {
|
|
69
|
-
return pageIdParam;
|
|
71
|
+
return { pageId: pageIdParam, spaceKey };
|
|
70
72
|
}
|
|
71
73
|
throw new Error('Could not extract Page ID from URL');
|
|
72
74
|
}
|
|
@@ -82,7 +84,7 @@ export function parseConfluenceUrl(url) {
|
|
|
82
84
|
*/
|
|
83
85
|
export async function getPage(url) {
|
|
84
86
|
const client = getConfluenceClient();
|
|
85
|
-
const pageId = parseConfluenceUrl(url);
|
|
87
|
+
const { pageId } = parseConfluenceUrl(url);
|
|
86
88
|
const page = await client.content.getContentById({
|
|
87
89
|
id: pageId,
|
|
88
90
|
expand: ['body.atlas_doc_format', 'version', 'space', 'history.lastUpdated'],
|
|
@@ -108,7 +110,7 @@ export async function getPage(url) {
|
|
|
108
110
|
*/
|
|
109
111
|
export async function getPageComments(url) {
|
|
110
112
|
const client = getConfluenceClient();
|
|
111
|
-
const pageId = parseConfluenceUrl(url);
|
|
113
|
+
const { pageId } = parseConfluenceUrl(url);
|
|
112
114
|
const response = await client.contentChildrenAndDescendants.getContentChildrenByType({
|
|
113
115
|
id: pageId,
|
|
114
116
|
type: 'comment',
|
package/dist/lib/formatters.js
CHANGED
|
@@ -461,17 +461,18 @@ export function formatWorklogs(worklogs, groupByIssue = false) {
|
|
|
461
461
|
return output;
|
|
462
462
|
}
|
|
463
463
|
/**
|
|
464
|
-
* Format settings
|
|
464
|
+
* Format organization settings
|
|
465
465
|
*/
|
|
466
|
-
|
|
467
|
-
let output =
|
|
468
|
-
|
|
469
|
-
output += chalk.bold('
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
466
|
+
function formatOrgSettings(settings, title) {
|
|
467
|
+
let output = chalk.bold(`${title}:`) + '\n';
|
|
468
|
+
const commands = settings['allowed-commands'] || [];
|
|
469
|
+
output += ` ${chalk.bold('Allowed Commands:')} ${commands.join(', ')}\n`;
|
|
470
|
+
const spaces = settings['allowed-confluence-spaces'] || [];
|
|
471
|
+
output += ` ${chalk.bold('Allowed Confluence Spaces:')} ${spaces.join(', ')}\n\n`;
|
|
472
|
+
const projects = settings['allowed-jira-projects'] || [];
|
|
473
|
+
output += ` ${chalk.bold(`Allowed Jira Projects (${projects.length}):`)}\n`;
|
|
473
474
|
const table = createTable(['Project', 'Commands', 'Filters'], [15, 30, 50]);
|
|
474
|
-
|
|
475
|
+
projects.forEach((p) => {
|
|
475
476
|
let key;
|
|
476
477
|
let commands = 'global';
|
|
477
478
|
let filters = 'none';
|
|
@@ -511,7 +512,23 @@ export function formatSettings(settings) {
|
|
|
511
512
|
filters
|
|
512
513
|
]);
|
|
513
514
|
});
|
|
514
|
-
output += table.toString() + '\n';
|
|
515
|
+
output += table.toString().split('\n').map(line => ' ' + line).join('\n') + '\n\n';
|
|
516
|
+
return output;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Format settings
|
|
520
|
+
*/
|
|
521
|
+
export function formatSettings(settings) {
|
|
522
|
+
let output = '\n' + chalk.bold.cyan('Active Configuration') + '\n\n';
|
|
523
|
+
if (settings.defaults) {
|
|
524
|
+
output += formatOrgSettings(settings.defaults, 'Default Settings');
|
|
525
|
+
}
|
|
526
|
+
if (settings.organizations && Object.keys(settings.organizations).length > 0) {
|
|
527
|
+
output += chalk.bold.blue('Organization-Specific Settings:') + '\n\n';
|
|
528
|
+
for (const [alias, orgSettings] of Object.entries(settings.organizations)) {
|
|
529
|
+
output += formatOrgSettings(orgSettings, `Organization: ${alias}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
515
532
|
return output;
|
|
516
533
|
}
|
|
517
534
|
/**
|
package/dist/lib/jira-client.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { Version3Client } from 'jira.js';
|
|
2
2
|
import { calculateStatusStatistics, convertADFToMarkdown } from './utils.js';
|
|
3
|
-
import { loadCredentials } from './auth-storage.js';
|
|
4
|
-
import { applyGlobalFilters, isProjectAllowed, isCommandAllowed, validateIssueAgainstFilters,
|
|
3
|
+
import { loadCredentials, getCurrentOrganizationAlias, setOrganizationOverride as setAuthOrgOverride } from './auth-storage.js';
|
|
4
|
+
import { applyGlobalFilters, isProjectAllowed, isCommandAllowed, validateIssueAgainstFilters, getAllowedProjects } from './settings.js';
|
|
5
5
|
import { CommandError } from './errors.js';
|
|
6
6
|
let jiraClient = null;
|
|
7
|
-
let organizationOverride = undefined;
|
|
8
7
|
/**
|
|
9
8
|
* Set a global organization override for the current execution
|
|
10
9
|
*/
|
|
11
10
|
export function setOrganizationOverride(alias) {
|
|
12
|
-
|
|
11
|
+
setAuthOrgOverride(alias);
|
|
13
12
|
jiraClient = null; // Force client recreation
|
|
14
13
|
}
|
|
15
14
|
/**
|
|
@@ -32,7 +31,8 @@ export function getJiraClient() {
|
|
|
32
31
|
});
|
|
33
32
|
}
|
|
34
33
|
else {
|
|
35
|
-
const
|
|
34
|
+
const alias = getCurrentOrganizationAlias();
|
|
35
|
+
const storedCreds = loadCredentials(alias);
|
|
36
36
|
if (storedCreds) {
|
|
37
37
|
jiraClient = new Version3Client({
|
|
38
38
|
host: storedCreds.host,
|
|
@@ -45,8 +45,8 @@ export function getJiraClient() {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
else {
|
|
48
|
-
const errorMsg =
|
|
49
|
-
? `Jira credentials for organization "${
|
|
48
|
+
const errorMsg = alias
|
|
49
|
+
? `Jira credentials for organization "${alias}" not found.`
|
|
50
50
|
: 'Jira credentials not found. Please set environment variables or run "jira-ai auth"';
|
|
51
51
|
throw new Error(errorMsg);
|
|
52
52
|
}
|
|
@@ -374,10 +374,10 @@ export async function validateIssuePermissions(issueKey, commandName, options =
|
|
|
374
374
|
});
|
|
375
375
|
}
|
|
376
376
|
// Check JQL filters
|
|
377
|
-
const
|
|
378
|
-
let project =
|
|
377
|
+
const allowedProjects = getAllowedProjects();
|
|
378
|
+
let project = allowedProjects.find(p => typeof p !== 'string' && p.key === projectKey);
|
|
379
379
|
if (!project) {
|
|
380
|
-
project =
|
|
380
|
+
project = allowedProjects.find(p => typeof p === 'string' && (p === 'all' || p === projectKey));
|
|
381
381
|
}
|
|
382
382
|
if (project && typeof project !== 'string' && project.filters?.jql) {
|
|
383
383
|
const client = getJiraClient();
|
package/dist/lib/settings.js
CHANGED
|
@@ -5,9 +5,10 @@ import yaml from 'js-yaml';
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { CliError } from '../types/errors.js';
|
|
7
7
|
import { SettingsSchema } from './validation.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { getCurrentOrganizationAlias } from './auth-storage.js';
|
|
9
|
+
export const DEFAULT_ORG_SETTINGS = {
|
|
10
|
+
'allowed-jira-projects': ['all'],
|
|
11
|
+
'allowed-commands': [
|
|
11
12
|
'me',
|
|
12
13
|
'projects',
|
|
13
14
|
'task-with-details',
|
|
@@ -24,8 +25,12 @@ export const DEFAULT_SETTINGS = {
|
|
|
24
25
|
'organization',
|
|
25
26
|
'transition',
|
|
26
27
|
'update-description',
|
|
27
|
-
'
|
|
28
|
-
]
|
|
28
|
+
'confluence'
|
|
29
|
+
],
|
|
30
|
+
'allowed-confluence-spaces': ['all']
|
|
31
|
+
};
|
|
32
|
+
export const DEFAULT_SETTINGS = {
|
|
33
|
+
defaults: DEFAULT_ORG_SETTINGS
|
|
29
34
|
};
|
|
30
35
|
const CONFIG_DIR = path.join(os.homedir(), '.jira-ai');
|
|
31
36
|
const SETTINGS_FILE = path.join(CONFIG_DIR, 'settings.yaml');
|
|
@@ -33,6 +38,25 @@ let cachedSettings = null;
|
|
|
33
38
|
export function getSettingsPath() {
|
|
34
39
|
return SETTINGS_FILE;
|
|
35
40
|
}
|
|
41
|
+
export function migrateSettings(settings) {
|
|
42
|
+
// Migration logic: if old structure exists, move it to defaults
|
|
43
|
+
if (settings.projects || settings.commands) {
|
|
44
|
+
const migratedDefaults = {
|
|
45
|
+
'allowed-jira-projects': settings.projects || DEFAULT_ORG_SETTINGS['allowed-jira-projects'],
|
|
46
|
+
'allowed-commands': settings.commands || DEFAULT_ORG_SETTINGS['allowed-commands'],
|
|
47
|
+
'allowed-confluence-spaces': DEFAULT_ORG_SETTINGS['allowed-confluence-spaces']
|
|
48
|
+
};
|
|
49
|
+
const newSettings = {
|
|
50
|
+
...settings,
|
|
51
|
+
defaults: migratedDefaults,
|
|
52
|
+
};
|
|
53
|
+
// Remove old fields
|
|
54
|
+
delete newSettings.projects;
|
|
55
|
+
delete newSettings.commands;
|
|
56
|
+
return newSettings;
|
|
57
|
+
}
|
|
58
|
+
return settings;
|
|
59
|
+
}
|
|
36
60
|
export function loadSettings() {
|
|
37
61
|
if (cachedSettings) {
|
|
38
62
|
return cachedSettings;
|
|
@@ -78,17 +102,13 @@ export function loadSettings() {
|
|
|
78
102
|
result.error.issues.forEach(issue => {
|
|
79
103
|
console.warn(chalk.yellow(` - ${issue.path.join('.')}: ${issue.message}`));
|
|
80
104
|
});
|
|
81
|
-
// Fallback to
|
|
82
|
-
|
|
83
|
-
cachedSettings = {
|
|
84
|
-
projects: settings?.projects || DEFAULT_SETTINGS.projects,
|
|
85
|
-
commands: settings?.commands || DEFAULT_SETTINGS.commands
|
|
86
|
-
};
|
|
105
|
+
// Fallback to defaults if parsing fails completely
|
|
106
|
+
return DEFAULT_SETTINGS;
|
|
87
107
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return
|
|
108
|
+
let settings = result.data;
|
|
109
|
+
settings = migrateSettings(settings);
|
|
110
|
+
cachedSettings = settings;
|
|
111
|
+
return settings;
|
|
92
112
|
}
|
|
93
113
|
catch (error) {
|
|
94
114
|
throw new CliError(`Error loading ${SETTINGS_FILE}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -108,9 +128,19 @@ export function saveSettings(settings) {
|
|
|
108
128
|
throw new CliError(`Error saving ${SETTINGS_FILE}: ${error instanceof Error ? error.message : String(error)}`);
|
|
109
129
|
}
|
|
110
130
|
}
|
|
111
|
-
|
|
131
|
+
function getEffectiveSettings(orgAlias) {
|
|
112
132
|
const settings = loadSettings();
|
|
113
|
-
const
|
|
133
|
+
const alias = orgAlias || getCurrentOrganizationAlias();
|
|
134
|
+
if (alias && settings.organizations && settings.organizations[alias]) {
|
|
135
|
+
return settings.organizations[alias];
|
|
136
|
+
}
|
|
137
|
+
return settings.defaults || null;
|
|
138
|
+
}
|
|
139
|
+
export function isProjectAllowed(projectKey, orgAlias) {
|
|
140
|
+
const settings = getEffectiveSettings(orgAlias);
|
|
141
|
+
if (!settings)
|
|
142
|
+
return false;
|
|
143
|
+
const isAllowed = settings['allowed-jira-projects'].some(p => {
|
|
114
144
|
if (typeof p === 'string') {
|
|
115
145
|
return p === 'all' || p === projectKey;
|
|
116
146
|
}
|
|
@@ -118,16 +148,18 @@ export function isProjectAllowed(projectKey) {
|
|
|
118
148
|
});
|
|
119
149
|
return isAllowed;
|
|
120
150
|
}
|
|
121
|
-
export function isCommandAllowed(commandName, projectKey) {
|
|
122
|
-
const settings = loadSettings();
|
|
151
|
+
export function isCommandAllowed(commandName, projectKey, orgAlias) {
|
|
123
152
|
// about, auth, and settings are always allowed
|
|
124
153
|
if (['about', 'auth', 'settings'].includes(commandName)) {
|
|
125
154
|
return true;
|
|
126
155
|
}
|
|
156
|
+
const settings = getEffectiveSettings(orgAlias);
|
|
157
|
+
if (!settings)
|
|
158
|
+
return false;
|
|
127
159
|
if (projectKey) {
|
|
128
|
-
let project = settings
|
|
160
|
+
let project = settings['allowed-jira-projects'].find(p => typeof p !== 'string' && p.key === projectKey);
|
|
129
161
|
if (!project) {
|
|
130
|
-
project = settings
|
|
162
|
+
project = settings['allowed-jira-projects'].find(p => typeof p === 'string' && (p === 'all' || p === projectKey));
|
|
131
163
|
}
|
|
132
164
|
if (project && typeof project !== 'string' && project.commands) {
|
|
133
165
|
return project.commands.includes(commandName);
|
|
@@ -135,32 +167,40 @@ export function isCommandAllowed(commandName, projectKey) {
|
|
|
135
167
|
}
|
|
136
168
|
else {
|
|
137
169
|
// For visibility/global check: allowed if in global list OR in any project-specific list
|
|
138
|
-
const allowedGlobally = settings
|
|
170
|
+
const allowedGlobally = settings['allowed-commands'].includes('all') || settings['allowed-commands'].includes(commandName);
|
|
139
171
|
if (allowedGlobally) {
|
|
140
172
|
return true;
|
|
141
173
|
}
|
|
142
|
-
const allowedInAnyProject = settings
|
|
174
|
+
const allowedInAnyProject = settings['allowed-jira-projects'].some(p => typeof p !== 'string' && p.commands && p.commands.includes(commandName));
|
|
143
175
|
if (allowedInAnyProject) {
|
|
144
176
|
return true;
|
|
145
177
|
}
|
|
146
178
|
return false;
|
|
147
179
|
}
|
|
148
|
-
if (settings
|
|
180
|
+
if (settings['allowed-commands'].includes('all')) {
|
|
149
181
|
return true;
|
|
150
182
|
}
|
|
151
|
-
return settings
|
|
183
|
+
return settings['allowed-commands'].includes(commandName);
|
|
152
184
|
}
|
|
153
|
-
export function
|
|
154
|
-
const settings =
|
|
155
|
-
|
|
185
|
+
export function isConfluenceSpaceAllowed(spaceKey, orgAlias) {
|
|
186
|
+
const settings = getEffectiveSettings(orgAlias);
|
|
187
|
+
if (!settings)
|
|
188
|
+
return false;
|
|
189
|
+
return settings['allowed-confluence-spaces'].some(s => s === 'all' || s === spaceKey);
|
|
156
190
|
}
|
|
157
|
-
export function
|
|
158
|
-
const settings =
|
|
159
|
-
return settings
|
|
191
|
+
export function getAllowedProjects(orgAlias) {
|
|
192
|
+
const settings = getEffectiveSettings(orgAlias);
|
|
193
|
+
return settings ? settings['allowed-jira-projects'] : [];
|
|
160
194
|
}
|
|
161
|
-
export function
|
|
162
|
-
const settings =
|
|
163
|
-
|
|
195
|
+
export function getAllowedCommands(orgAlias) {
|
|
196
|
+
const settings = getEffectiveSettings(orgAlias);
|
|
197
|
+
return settings ? settings['allowed-commands'] : [];
|
|
198
|
+
}
|
|
199
|
+
export function applyGlobalFilters(jql, orgAlias) {
|
|
200
|
+
const settings = getEffectiveSettings(orgAlias);
|
|
201
|
+
if (!settings)
|
|
202
|
+
return jql;
|
|
203
|
+
const allAllowed = settings['allowed-jira-projects'].some(p => p === 'all');
|
|
164
204
|
if (allAllowed) {
|
|
165
205
|
return jql;
|
|
166
206
|
}
|
|
@@ -172,7 +212,7 @@ export function applyGlobalFilters(jql) {
|
|
|
172
212
|
filterPart = orderByMatch[1].trim();
|
|
173
213
|
orderByPart = ` ORDER BY ${orderByMatch[2].trim()}`;
|
|
174
214
|
}
|
|
175
|
-
const projectFilters = settings
|
|
215
|
+
const projectFilters = settings['allowed-jira-projects'].map(p => {
|
|
176
216
|
const key = typeof p === 'string' ? p : p.key;
|
|
177
217
|
const projectJql = typeof p === 'string' ? null : p.filters?.jql;
|
|
178
218
|
if (projectJql) {
|
|
@@ -190,14 +230,16 @@ export function applyGlobalFilters(jql) {
|
|
|
190
230
|
const filterJql = filterPart.trim() ? ` AND (${filterPart})` : '';
|
|
191
231
|
return `(${combinedProjectFilter})${filterJql}${orderByPart}`;
|
|
192
232
|
}
|
|
193
|
-
export function validateIssueAgainstFilters(issue, currentUserId) {
|
|
194
|
-
const settings =
|
|
233
|
+
export function validateIssueAgainstFilters(issue, currentUserId, orgAlias) {
|
|
234
|
+
const settings = getEffectiveSettings(orgAlias);
|
|
235
|
+
if (!settings)
|
|
236
|
+
return false;
|
|
195
237
|
const projectKey = issue.key.split('-')[0];
|
|
196
238
|
// Find specific project config first
|
|
197
|
-
let project = settings
|
|
239
|
+
let project = settings['allowed-jira-projects'].find(p => typeof p !== 'string' && p.key === projectKey);
|
|
198
240
|
// If not found, look for string match (exact project key or 'all')
|
|
199
241
|
if (!project) {
|
|
200
|
-
project = settings
|
|
242
|
+
project = settings['allowed-jira-projects'].find(p => typeof p === 'string' && (p === 'all' || p === projectKey));
|
|
201
243
|
}
|
|
202
244
|
if (!project) {
|
|
203
245
|
return false;
|
package/dist/lib/validation.js
CHANGED
|
@@ -93,7 +93,15 @@ export const ProjectSettingSchema = z.union([
|
|
|
93
93
|
z.string().trim().min(1),
|
|
94
94
|
ProjectConfigSchema
|
|
95
95
|
]);
|
|
96
|
+
export const OrganizationSettingsSchema = z.object({
|
|
97
|
+
'allowed-jira-projects': z.array(ProjectSettingSchema).nullish().transform(val => val || ['all']),
|
|
98
|
+
'allowed-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', 'confluence']),
|
|
99
|
+
'allowed-confluence-spaces': z.array(z.string()).nullish().transform(val => val || ['all']),
|
|
100
|
+
});
|
|
96
101
|
export const SettingsSchema = z.object({
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
defaults: OrganizationSettingsSchema.optional(),
|
|
103
|
+
organizations: z.record(z.string(), OrganizationSettingsSchema).optional(),
|
|
104
|
+
// Keep legacy fields for migration
|
|
105
|
+
projects: z.array(ProjectSettingSchema).optional(),
|
|
106
|
+
commands: z.array(z.string()).optional(),
|
|
99
107
|
});
|