@hasna/connectors 0.3.16 → 0.4.0
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/bin/index.js +71 -1
- package/bin/mcp.js +71 -1
- package/bin/serve.js +70 -0
- package/connectors/connect-asana/.env.example +11 -0
- package/connectors/connect-asana/CLAUDE.md +128 -0
- package/connectors/connect-asana/README.md +193 -0
- package/connectors/connect-asana/package.json +52 -0
- package/connectors/connect-asana/src/api/client.ts +119 -0
- package/connectors/connect-asana/src/api/index.ts +319 -0
- package/connectors/connect-asana/src/cli/index.ts +731 -0
- package/connectors/connect-asana/src/index.ts +19 -0
- package/connectors/connect-asana/src/types/index.ts +270 -0
- package/connectors/connect-asana/src/utils/config.ts +171 -0
- package/connectors/connect-asana/src/utils/output.ts +119 -0
- package/connectors/connect-asana/tsconfig.json +16 -0
- package/connectors/connect-clickup/.env.example +11 -0
- package/connectors/connect-clickup/CLAUDE.md +128 -0
- package/connectors/connect-clickup/README.md +193 -0
- package/connectors/connect-clickup/package.json +52 -0
- package/connectors/connect-clickup/src/api/client.ts +116 -0
- package/connectors/connect-clickup/src/api/index.ts +400 -0
- package/connectors/connect-clickup/src/cli/index.ts +625 -0
- package/connectors/connect-clickup/src/index.ts +19 -0
- package/connectors/connect-clickup/src/types/index.ts +591 -0
- package/connectors/connect-clickup/src/utils/config.ts +157 -0
- package/connectors/connect-clickup/src/utils/output.ts +119 -0
- package/connectors/connect-clickup/tsconfig.json +16 -0
- package/connectors/connect-confluence/.env.example +11 -0
- package/connectors/connect-confluence/CLAUDE.md +272 -0
- package/connectors/connect-confluence/README.md +193 -0
- package/connectors/connect-confluence/package.json +53 -0
- package/connectors/connect-confluence/scripts/release.ts +179 -0
- package/connectors/connect-confluence/src/api/client.ts +213 -0
- package/connectors/connect-confluence/src/api/example.ts +48 -0
- package/connectors/connect-confluence/src/api/index.ts +51 -0
- package/connectors/connect-confluence/src/cli/index.ts +254 -0
- package/connectors/connect-confluence/src/index.ts +103 -0
- package/connectors/connect-confluence/src/types/index.ts +237 -0
- package/connectors/connect-confluence/src/utils/auth.ts +274 -0
- package/connectors/connect-confluence/src/utils/bulk.ts +212 -0
- package/connectors/connect-confluence/src/utils/config.ts +326 -0
- package/connectors/connect-confluence/src/utils/output.ts +175 -0
- package/connectors/connect-confluence/src/utils/settings.ts +114 -0
- package/connectors/connect-confluence/src/utils/storage.ts +198 -0
- package/connectors/connect-confluence/tsconfig.json +16 -0
- package/connectors/connect-jira/.env.example +11 -0
- package/connectors/connect-jira/CLAUDE.md +128 -0
- package/connectors/connect-jira/README.md +193 -0
- package/connectors/connect-jira/package.json +53 -0
- package/connectors/connect-jira/src/api/client.ts +131 -0
- package/connectors/connect-jira/src/api/index.ts +266 -0
- package/connectors/connect-jira/src/cli/index.ts +653 -0
- package/connectors/connect-jira/src/index.ts +23 -0
- package/connectors/connect-jira/src/types/index.ts +448 -0
- package/connectors/connect-jira/src/utils/config.ts +179 -0
- package/connectors/connect-jira/src/utils/output.ts +119 -0
- package/connectors/connect-jira/tsconfig.json +16 -0
- package/connectors/connect-linear/CLAUDE.md +88 -0
- package/connectors/connect-linear/README.md +201 -0
- package/connectors/connect-linear/package.json +45 -0
- package/connectors/connect-linear/src/api/client.ts +62 -0
- package/connectors/connect-linear/src/api/index.ts +46 -0
- package/connectors/connect-linear/src/api/issues.ts +247 -0
- package/connectors/connect-linear/src/api/projects.ts +179 -0
- package/connectors/connect-linear/src/api/teams.ts +125 -0
- package/connectors/connect-linear/src/api/users.ts +112 -0
- package/connectors/connect-linear/src/cli/index.ts +560 -0
- package/connectors/connect-linear/src/index.ts +27 -0
- package/connectors/connect-linear/src/types/index.ts +275 -0
- package/connectors/connect-linear/src/utils/config.ts +249 -0
- package/connectors/connect-linear/src/utils/output.ts +119 -0
- package/connectors/connect-linear/tsconfig.json +16 -0
- package/connectors/connect-slack/.env.example +7 -0
- package/connectors/connect-slack/CLAUDE.md +69 -0
- package/connectors/connect-slack/README.md +150 -0
- package/connectors/connect-slack/package.json +44 -0
- package/connectors/connect-slack/src/api/channels.ts +112 -0
- package/connectors/connect-slack/src/api/client.ts +97 -0
- package/connectors/connect-slack/src/api/index.ts +42 -0
- package/connectors/connect-slack/src/api/messages.ts +127 -0
- package/connectors/connect-slack/src/api/users.ts +110 -0
- package/connectors/connect-slack/src/cli/index.ts +494 -0
- package/connectors/connect-slack/src/index.ts +21 -0
- package/connectors/connect-slack/src/types/index.ts +263 -0
- package/connectors/connect-slack/src/utils/config.ts +297 -0
- package/connectors/connect-slack/src/utils/output.ts +119 -0
- package/connectors/connect-slack/tsconfig.json +16 -0
- package/connectors/connect-telegram/.env.example +2 -0
- package/connectors/connect-telegram/package.json +49 -0
- package/connectors/connect-todoist/.env.example +11 -0
- package/connectors/connect-todoist/CLAUDE.md +104 -0
- package/connectors/connect-todoist/README.md +193 -0
- package/connectors/connect-todoist/package.json +52 -0
- package/connectors/connect-todoist/src/api/client.ts +117 -0
- package/connectors/connect-todoist/src/api/index.ts +188 -0
- package/connectors/connect-todoist/src/cli/index.ts +990 -0
- package/connectors/connect-todoist/src/index.ts +21 -0
- package/connectors/connect-todoist/src/types/index.ts +240 -0
- package/connectors/connect-todoist/src/utils/config.ts +157 -0
- package/connectors/connect-todoist/src/utils/output.ts +119 -0
- package/connectors/connect-todoist/tsconfig.json +16 -0
- package/connectors/connect-trello/.env.example +11 -0
- package/connectors/connect-trello/CLAUDE.md +128 -0
- package/connectors/connect-trello/README.md +193 -0
- package/connectors/connect-trello/package.json +53 -0
- package/connectors/connect-trello/src/api/client.ts +128 -0
- package/connectors/connect-trello/src/api/index.ts +278 -0
- package/connectors/connect-trello/src/cli/index.ts +737 -0
- package/connectors/connect-trello/src/index.ts +21 -0
- package/connectors/connect-trello/src/types/index.ts +314 -0
- package/connectors/connect-trello/src/utils/config.ts +182 -0
- package/connectors/connect-trello/src/utils/output.ts +119 -0
- package/connectors/connect-trello/tsconfig.json +16 -0
- package/connectors/connect-whatsapp/.env.example +11 -0
- package/connectors/connect-whatsapp/CLAUDE.md +113 -0
- package/connectors/connect-whatsapp/README.md +193 -0
- package/connectors/connect-whatsapp/package.json +53 -0
- package/connectors/connect-whatsapp/src/api/client.ts +133 -0
- package/connectors/connect-whatsapp/src/api/index.ts +365 -0
- package/connectors/connect-whatsapp/src/cli/index.ts +686 -0
- package/connectors/connect-whatsapp/src/index.ts +25 -0
- package/connectors/connect-whatsapp/src/types/index.ts +502 -0
- package/connectors/connect-whatsapp/src/utils/config.ts +179 -0
- package/connectors/connect-whatsapp/src/utils/output.ts +119 -0
- package/connectors/connect-whatsapp/tsconfig.json +16 -0
- package/dist/index.js +70 -0
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { JiraConfig, JiraError } from '../types';
|
|
2
|
+
import { JiraApiError } from '../types';
|
|
3
|
+
|
|
4
|
+
export interface RequestOptions {
|
|
5
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
6
|
+
params?: Record<string, string | number | boolean | undefined>;
|
|
7
|
+
body?: Record<string, unknown> | unknown[] | string;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class JiraClient {
|
|
12
|
+
private readonly email: string;
|
|
13
|
+
private readonly apiToken: string;
|
|
14
|
+
private readonly baseUrl: string;
|
|
15
|
+
|
|
16
|
+
constructor(config: JiraConfig) {
|
|
17
|
+
if (!config.email) {
|
|
18
|
+
throw new Error('Email is required');
|
|
19
|
+
}
|
|
20
|
+
if (!config.apiToken) {
|
|
21
|
+
throw new Error('API token is required');
|
|
22
|
+
}
|
|
23
|
+
if (!config.domain) {
|
|
24
|
+
throw new Error('Domain is required');
|
|
25
|
+
}
|
|
26
|
+
this.email = config.email;
|
|
27
|
+
this.apiToken = config.apiToken;
|
|
28
|
+
this.baseUrl = config.baseUrl || `https://${config.domain}/rest/api/3`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private getAuthHeader(): string {
|
|
32
|
+
const credentials = `${this.email}:${this.apiToken}`;
|
|
33
|
+
const encoded = Buffer.from(credentials).toString('base64');
|
|
34
|
+
return `Basic ${encoded}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {
|
|
38
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
39
|
+
|
|
40
|
+
if (params) {
|
|
41
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
42
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
43
|
+
url.searchParams.append(key, String(value));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return url.toString();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async request<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
|
52
|
+
const { method = 'GET', params, body, headers = {} } = options;
|
|
53
|
+
|
|
54
|
+
const url = this.buildUrl(path, params);
|
|
55
|
+
|
|
56
|
+
const requestHeaders: Record<string, string> = {
|
|
57
|
+
'Authorization': this.getAuthHeader(),
|
|
58
|
+
'Accept': 'application/json',
|
|
59
|
+
...headers,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
63
|
+
requestHeaders['Content-Type'] = 'application/json';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const fetchOptions: RequestInit = {
|
|
67
|
+
method,
|
|
68
|
+
headers: requestHeaders,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
72
|
+
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const response = await fetch(url, fetchOptions);
|
|
76
|
+
|
|
77
|
+
if (response.status === 204) {
|
|
78
|
+
return {} as T;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let data: unknown;
|
|
82
|
+
const contentType = response.headers.get('content-type') || '';
|
|
83
|
+
|
|
84
|
+
if (contentType.includes('application/json')) {
|
|
85
|
+
const text = await response.text();
|
|
86
|
+
if (text) {
|
|
87
|
+
try {
|
|
88
|
+
data = JSON.parse(text);
|
|
89
|
+
} catch {
|
|
90
|
+
data = text;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
data = await response.text();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const errorData = data as JiraError | null;
|
|
99
|
+
const errorMessage = errorData?.errorMessages?.[0] ||
|
|
100
|
+
(errorData?.errors ? Object.values(errorData.errors).join(', ') : '') ||
|
|
101
|
+
response.statusText;
|
|
102
|
+
throw new JiraApiError(errorMessage, response.status, errorData || undefined);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return data as T;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async get<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
|
109
|
+
return this.request<T>(path, { method: 'GET', params });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async post<T>(path: string, body?: Record<string, unknown> | unknown[] | string | object, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
|
113
|
+
return this.request<T>(path, { method: 'POST', body: body as Record<string, unknown>, params });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async put<T>(path: string, body?: Record<string, unknown> | object, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
|
117
|
+
return this.request<T>(path, { method: 'PUT', body: body as Record<string, unknown>, params });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async delete<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
|
121
|
+
return this.request<T>(path, { method: 'DELETE', params });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
getEmailPreview(): string {
|
|
125
|
+
const [local, domain] = this.email.split('@');
|
|
126
|
+
if (local && domain) {
|
|
127
|
+
return `${local.substring(0, 3)}...@${domain}`;
|
|
128
|
+
}
|
|
129
|
+
return '***@***';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// Jira Connector
|
|
2
|
+
// Projects, issues, boards, and sprints management
|
|
3
|
+
|
|
4
|
+
import { JiraClient } from './client';
|
|
5
|
+
import type {
|
|
6
|
+
JiraConfig,
|
|
7
|
+
User,
|
|
8
|
+
Project,
|
|
9
|
+
Issue,
|
|
10
|
+
Comment,
|
|
11
|
+
Transition,
|
|
12
|
+
Board,
|
|
13
|
+
Sprint,
|
|
14
|
+
SearchResponse,
|
|
15
|
+
PaginatedResponse,
|
|
16
|
+
CreateIssueInput,
|
|
17
|
+
UpdateIssueInput,
|
|
18
|
+
TransitionIssueInput,
|
|
19
|
+
CreateCommentInput,
|
|
20
|
+
} from '../types';
|
|
21
|
+
|
|
22
|
+
export { JiraClient } from './client';
|
|
23
|
+
|
|
24
|
+
export class Jira {
|
|
25
|
+
private client: JiraClient;
|
|
26
|
+
private agileBaseUrl: string;
|
|
27
|
+
|
|
28
|
+
constructor(config: JiraConfig) {
|
|
29
|
+
this.client = new JiraClient(config);
|
|
30
|
+
this.agileBaseUrl = config.baseUrl?.replace('/rest/api/3', '/rest/agile/1.0') ||
|
|
31
|
+
`https://${config.domain}/rest/agile/1.0`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ============================================
|
|
35
|
+
// User Operations
|
|
36
|
+
// ============================================
|
|
37
|
+
|
|
38
|
+
async getMyself(): Promise<User> {
|
|
39
|
+
return this.client.get<User>('/myself');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async searchUsers(options: {
|
|
43
|
+
query?: string;
|
|
44
|
+
accountId?: string;
|
|
45
|
+
startAt?: number;
|
|
46
|
+
maxResults?: number;
|
|
47
|
+
}): Promise<User[]> {
|
|
48
|
+
return this.client.get<User[]>('/user/search', options);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================
|
|
52
|
+
// Project Operations
|
|
53
|
+
// ============================================
|
|
54
|
+
|
|
55
|
+
async listProjects(options?: {
|
|
56
|
+
startAt?: number;
|
|
57
|
+
maxResults?: number;
|
|
58
|
+
orderBy?: string;
|
|
59
|
+
expand?: string;
|
|
60
|
+
}): Promise<PaginatedResponse<Project>> {
|
|
61
|
+
return this.client.get<PaginatedResponse<Project>>('/project/search', options);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getProject(projectIdOrKey: string, options?: { expand?: string }): Promise<Project> {
|
|
65
|
+
return this.client.get<Project>(`/project/${projectIdOrKey}`, options);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================
|
|
69
|
+
// Issue Operations
|
|
70
|
+
// ============================================
|
|
71
|
+
|
|
72
|
+
async searchIssues(options: {
|
|
73
|
+
jql: string;
|
|
74
|
+
startAt?: number;
|
|
75
|
+
maxResults?: number;
|
|
76
|
+
fields?: string[];
|
|
77
|
+
expand?: string;
|
|
78
|
+
}): Promise<SearchResponse<Issue>> {
|
|
79
|
+
return this.client.post<SearchResponse<Issue>>('/search', {
|
|
80
|
+
jql: options.jql,
|
|
81
|
+
startAt: options.startAt,
|
|
82
|
+
maxResults: options.maxResults,
|
|
83
|
+
fields: options.fields,
|
|
84
|
+
expand: options.expand,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getIssue(issueIdOrKey: string, options?: {
|
|
89
|
+
fields?: string[];
|
|
90
|
+
expand?: string;
|
|
91
|
+
}): Promise<Issue> {
|
|
92
|
+
return this.client.get<Issue>(`/issue/${issueIdOrKey}`, {
|
|
93
|
+
fields: options?.fields?.join(','),
|
|
94
|
+
expand: options?.expand,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async createIssue(input: CreateIssueInput): Promise<Issue> {
|
|
99
|
+
return this.client.post<Issue>('/issue', input);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async updateIssue(issueIdOrKey: string, input: UpdateIssueInput): Promise<void> {
|
|
103
|
+
await this.client.put(`/issue/${issueIdOrKey}`, input);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async deleteIssue(issueIdOrKey: string, options?: { deleteSubtasks?: boolean }): Promise<void> {
|
|
107
|
+
await this.client.delete(`/issue/${issueIdOrKey}`, {
|
|
108
|
+
deleteSubtasks: options?.deleteSubtasks,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async assignIssue(issueIdOrKey: string, accountId: string | null): Promise<void> {
|
|
113
|
+
await this.client.put(`/issue/${issueIdOrKey}/assignee`, { accountId });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ============================================
|
|
117
|
+
// Transition Operations
|
|
118
|
+
// ============================================
|
|
119
|
+
|
|
120
|
+
async getTransitions(issueIdOrKey: string): Promise<{ transitions: Transition[] }> {
|
|
121
|
+
return this.client.get<{ transitions: Transition[] }>(`/issue/${issueIdOrKey}/transitions`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async transitionIssue(issueIdOrKey: string, input: TransitionIssueInput): Promise<void> {
|
|
125
|
+
await this.client.post(`/issue/${issueIdOrKey}/transitions`, input);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================
|
|
129
|
+
// Comment Operations
|
|
130
|
+
// ============================================
|
|
131
|
+
|
|
132
|
+
async getComments(issueIdOrKey: string, options?: {
|
|
133
|
+
startAt?: number;
|
|
134
|
+
maxResults?: number;
|
|
135
|
+
orderBy?: string;
|
|
136
|
+
expand?: string;
|
|
137
|
+
}): Promise<{ comments: Comment[]; startAt: number; maxResults: number; total: number }> {
|
|
138
|
+
return this.client.get(`/issue/${issueIdOrKey}/comment`, options);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async addComment(issueIdOrKey: string, input: CreateCommentInput): Promise<Comment> {
|
|
142
|
+
return this.client.post<Comment>(`/issue/${issueIdOrKey}/comment`, input);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async updateComment(issueIdOrKey: string, commentId: string, input: CreateCommentInput): Promise<Comment> {
|
|
146
|
+
return this.client.put<Comment>(`/issue/${issueIdOrKey}/comment/${commentId}`, input);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async deleteComment(issueIdOrKey: string, commentId: string): Promise<void> {
|
|
150
|
+
await this.client.delete(`/issue/${issueIdOrKey}/comment/${commentId}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================
|
|
154
|
+
// Board Operations (Agile)
|
|
155
|
+
// ============================================
|
|
156
|
+
|
|
157
|
+
async listBoards(options?: {
|
|
158
|
+
startAt?: number;
|
|
159
|
+
maxResults?: number;
|
|
160
|
+
type?: 'scrum' | 'kanban' | 'simple';
|
|
161
|
+
name?: string;
|
|
162
|
+
projectKeyOrId?: string;
|
|
163
|
+
}): Promise<PaginatedResponse<Board>> {
|
|
164
|
+
const url = this.agileBaseUrl + '/board';
|
|
165
|
+
const response = await fetch(this.buildAgileUrl(url, options), {
|
|
166
|
+
headers: this.getAgileHeaders(),
|
|
167
|
+
});
|
|
168
|
+
return response.json();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async getBoard(boardId: number): Promise<Board> {
|
|
172
|
+
const url = this.agileBaseUrl + `/board/${boardId}`;
|
|
173
|
+
const response = await fetch(url, {
|
|
174
|
+
headers: this.getAgileHeaders(),
|
|
175
|
+
});
|
|
176
|
+
return response.json();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async getBoardIssues(boardId: number, options?: {
|
|
180
|
+
startAt?: number;
|
|
181
|
+
maxResults?: number;
|
|
182
|
+
jql?: string;
|
|
183
|
+
}): Promise<SearchResponse<Issue>> {
|
|
184
|
+
const url = this.agileBaseUrl + `/board/${boardId}/issue`;
|
|
185
|
+
const response = await fetch(this.buildAgileUrl(url, options), {
|
|
186
|
+
headers: this.getAgileHeaders(),
|
|
187
|
+
});
|
|
188
|
+
return response.json();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ============================================
|
|
192
|
+
// Sprint Operations
|
|
193
|
+
// ============================================
|
|
194
|
+
|
|
195
|
+
async listSprints(boardId: number, options?: {
|
|
196
|
+
startAt?: number;
|
|
197
|
+
maxResults?: number;
|
|
198
|
+
state?: 'future' | 'active' | 'closed';
|
|
199
|
+
}): Promise<PaginatedResponse<Sprint>> {
|
|
200
|
+
const url = this.agileBaseUrl + `/board/${boardId}/sprint`;
|
|
201
|
+
const response = await fetch(this.buildAgileUrl(url, options), {
|
|
202
|
+
headers: this.getAgileHeaders(),
|
|
203
|
+
});
|
|
204
|
+
return response.json();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async getSprint(sprintId: number): Promise<Sprint> {
|
|
208
|
+
const url = this.agileBaseUrl + `/sprint/${sprintId}`;
|
|
209
|
+
const response = await fetch(url, {
|
|
210
|
+
headers: this.getAgileHeaders(),
|
|
211
|
+
});
|
|
212
|
+
return response.json();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async getSprintIssues(sprintId: number, options?: {
|
|
216
|
+
startAt?: number;
|
|
217
|
+
maxResults?: number;
|
|
218
|
+
jql?: string;
|
|
219
|
+
}): Promise<SearchResponse<Issue>> {
|
|
220
|
+
const url = this.agileBaseUrl + `/sprint/${sprintId}/issue`;
|
|
221
|
+
const response = await fetch(this.buildAgileUrl(url, options), {
|
|
222
|
+
headers: this.getAgileHeaders(),
|
|
223
|
+
});
|
|
224
|
+
return response.json();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async moveIssuesToSprint(sprintId: number, issueKeys: string[]): Promise<void> {
|
|
228
|
+
const url = this.agileBaseUrl + `/sprint/${sprintId}/issue`;
|
|
229
|
+
await fetch(url, {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
headers: {
|
|
232
|
+
...this.getAgileHeaders(),
|
|
233
|
+
'Content-Type': 'application/json',
|
|
234
|
+
},
|
|
235
|
+
body: JSON.stringify({ issues: issueKeys }),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ============================================
|
|
240
|
+
// Helper Methods
|
|
241
|
+
// ============================================
|
|
242
|
+
|
|
243
|
+
private getAgileHeaders(): Record<string, string> {
|
|
244
|
+
const credentials = Buffer.from(`${(this.client as any).email}:${(this.client as any).apiToken}`).toString('base64');
|
|
245
|
+
return {
|
|
246
|
+
'Authorization': `Basic ${credentials}`,
|
|
247
|
+
'Accept': 'application/json',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private buildAgileUrl(baseUrl: string, params?: Record<string, string | number | boolean | undefined>): string {
|
|
252
|
+
const url = new URL(baseUrl);
|
|
253
|
+
if (params) {
|
|
254
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
255
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
256
|
+
url.searchParams.append(key, String(value));
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return url.toString();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
getClient(): JiraClient {
|
|
264
|
+
return this.client;
|
|
265
|
+
}
|
|
266
|
+
}
|