jira-datacenter-api-client 1.0.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/LICENSE +21 -0
- package/README.md +274 -0
- package/dist/index.d.mts +1929 -0
- package/dist/index.d.ts +1929 -0
- package/dist/index.js +886 -0
- package/dist/index.mjs +863 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BoardResource: () => BoardResource,
|
|
24
|
+
IssueResource: () => IssueResource,
|
|
25
|
+
JiraApiError: () => JiraApiError,
|
|
26
|
+
JiraClient: () => JiraClient,
|
|
27
|
+
ProjectResource: () => ProjectResource,
|
|
28
|
+
Security: () => Security,
|
|
29
|
+
SprintResource: () => SprintResource
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/security/Security.ts
|
|
34
|
+
function toBase64(value) {
|
|
35
|
+
if (typeof btoa !== "undefined") {
|
|
36
|
+
return btoa(value);
|
|
37
|
+
}
|
|
38
|
+
return Buffer.from(value).toString("base64");
|
|
39
|
+
}
|
|
40
|
+
var Security = class {
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new Security instance with Basic Authentication credentials.
|
|
43
|
+
*
|
|
44
|
+
* @param apiUrl - The base URL of the Jira Data Center instance (e.g., `https://jira.example.com`).
|
|
45
|
+
* Must be a valid URL; throws if it cannot be parsed.
|
|
46
|
+
* @param user - The username to authenticate with
|
|
47
|
+
* @param token - The personal access token or password to authenticate with
|
|
48
|
+
*
|
|
49
|
+
* @throws {TypeError} If `apiUrl` is not a valid URL
|
|
50
|
+
*/
|
|
51
|
+
constructor(apiUrl, user, token) {
|
|
52
|
+
if (!URL.canParse(apiUrl)) {
|
|
53
|
+
throw new TypeError(`Invalid apiUrl: "${apiUrl}" is not a valid URL`);
|
|
54
|
+
}
|
|
55
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
56
|
+
this.authorizationHeader = `Basic ${toBase64(`${user}:${token}`)}`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns the base URL of the Jira Data Center instance, without a trailing slash.
|
|
60
|
+
*
|
|
61
|
+
* @returns The API base URL
|
|
62
|
+
*/
|
|
63
|
+
getApiUrl() {
|
|
64
|
+
return this.apiUrl;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Returns the value of the `Authorization` header for Basic Authentication.
|
|
68
|
+
*
|
|
69
|
+
* @returns The Authorization header value in the format `Basic <base64-encoded-credentials>`
|
|
70
|
+
*/
|
|
71
|
+
getAuthorizationHeader() {
|
|
72
|
+
return this.authorizationHeader;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Returns the full set of HTTP headers required for authenticated API requests.
|
|
76
|
+
*
|
|
77
|
+
* @returns An object containing `Authorization`, `Content-Type`, and `Accept` headers
|
|
78
|
+
*/
|
|
79
|
+
getHeaders() {
|
|
80
|
+
return {
|
|
81
|
+
Authorization: this.authorizationHeader,
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
Accept: "application/json"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/errors/JiraApiError.ts
|
|
89
|
+
var JiraApiError = class extends Error {
|
|
90
|
+
constructor(status, statusText) {
|
|
91
|
+
super(`Jira API error: ${status} ${statusText}`);
|
|
92
|
+
this.name = "JiraApiError";
|
|
93
|
+
this.status = status;
|
|
94
|
+
this.statusText = statusText;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/resources/IssueResource.ts
|
|
99
|
+
var IssueResource = class {
|
|
100
|
+
/** @internal */
|
|
101
|
+
constructor(request, issueIdOrKey) {
|
|
102
|
+
this.request = request;
|
|
103
|
+
this.basePath = `/issue/${issueIdOrKey}`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Allows the resource to be awaited directly, resolving with the issue.
|
|
107
|
+
* Delegates to {@link IssueResource.get}.
|
|
108
|
+
*/
|
|
109
|
+
then(onfulfilled, onrejected) {
|
|
110
|
+
return this.get().then(onfulfilled, onrejected);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Fetches the issue.
|
|
114
|
+
*
|
|
115
|
+
* `GET /rest/api/latest/issue/{issueIdOrKey}`
|
|
116
|
+
*
|
|
117
|
+
* @param params - Optional: `fields`, `expand`, `properties`, `updateHistory`
|
|
118
|
+
* @returns The issue object
|
|
119
|
+
*/
|
|
120
|
+
async get(params) {
|
|
121
|
+
return this.request(
|
|
122
|
+
this.basePath,
|
|
123
|
+
params
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Fetches comments for this issue.
|
|
128
|
+
*
|
|
129
|
+
* `GET /rest/api/latest/issue/{issueIdOrKey}/comment`
|
|
130
|
+
*
|
|
131
|
+
* @param params - Optional: `startAt`, `maxResults`, `orderBy`, `expand`
|
|
132
|
+
* @returns A paged response of comments
|
|
133
|
+
*/
|
|
134
|
+
async comments(params) {
|
|
135
|
+
return this.request(
|
|
136
|
+
`${this.basePath}/comment`,
|
|
137
|
+
params
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Fetches a single comment by ID.
|
|
142
|
+
*
|
|
143
|
+
* `GET /rest/api/latest/issue/{issueIdOrKey}/comment/{id}`
|
|
144
|
+
*
|
|
145
|
+
* @param commentId - The numeric comment ID
|
|
146
|
+
* @returns The comment object
|
|
147
|
+
*/
|
|
148
|
+
async comment(commentId) {
|
|
149
|
+
return this.request(`${this.basePath}/comment/${commentId}`);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Fetches worklogs for this issue.
|
|
153
|
+
*
|
|
154
|
+
* `GET /rest/api/latest/issue/{issueIdOrKey}/worklog`
|
|
155
|
+
*
|
|
156
|
+
* @param params - Optional: `startAt`, `maxResults`, `expand`
|
|
157
|
+
* @returns A paged response of worklogs
|
|
158
|
+
*/
|
|
159
|
+
async worklogs(params) {
|
|
160
|
+
return this.request(
|
|
161
|
+
`${this.basePath}/worklog`,
|
|
162
|
+
params
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Fetches a single worklog by ID.
|
|
167
|
+
*
|
|
168
|
+
* `GET /rest/api/latest/issue/{issueIdOrKey}/worklog/{id}`
|
|
169
|
+
*
|
|
170
|
+
* @param worklogId - The numeric worklog ID
|
|
171
|
+
* @returns The worklog object
|
|
172
|
+
*/
|
|
173
|
+
async worklog(worklogId) {
|
|
174
|
+
return this.request(`${this.basePath}/worklog/${worklogId}`);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Fetches the changelog for this issue.
|
|
178
|
+
*
|
|
179
|
+
* `GET /rest/api/latest/issue/{issueIdOrKey}/changelog`
|
|
180
|
+
*
|
|
181
|
+
* @param params - Optional: `startAt`, `maxResults`
|
|
182
|
+
* @returns A paged response of changelog entries
|
|
183
|
+
*/
|
|
184
|
+
async changelog(params) {
|
|
185
|
+
return this.request(
|
|
186
|
+
`${this.basePath}/changelog`,
|
|
187
|
+
params
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Fetches the available workflow transitions for this issue.
|
|
192
|
+
*
|
|
193
|
+
* `GET /rest/api/latest/issue/{issueIdOrKey}/transitions`
|
|
194
|
+
*
|
|
195
|
+
* @param params - Optional: `transitionId`, `expand`, `skipRemoteOnlyCondition`
|
|
196
|
+
* @returns An object containing the list of transitions
|
|
197
|
+
*/
|
|
198
|
+
async transitions(params) {
|
|
199
|
+
return this.request(
|
|
200
|
+
`${this.basePath}/transitions`,
|
|
201
|
+
params
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Fetches the remote links (external URLs) for this issue.
|
|
206
|
+
*
|
|
207
|
+
* `GET /rest/api/latest/issue/{issueIdOrKey}/remotelink`
|
|
208
|
+
*
|
|
209
|
+
* @param globalId - Optional global ID to filter by a specific remote link
|
|
210
|
+
* @returns An array of remote links
|
|
211
|
+
*/
|
|
212
|
+
async remotelinks(globalId) {
|
|
213
|
+
return this.request(
|
|
214
|
+
`${this.basePath}/remotelink`,
|
|
215
|
+
globalId !== void 0 ? { globalId } : void 0
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Fetches the vote information for this issue.
|
|
220
|
+
*
|
|
221
|
+
* `GET /rest/api/latest/issue/{issueIdOrKey}/votes`
|
|
222
|
+
*
|
|
223
|
+
* @returns The votes object
|
|
224
|
+
*/
|
|
225
|
+
async votes() {
|
|
226
|
+
return this.request(`${this.basePath}/votes`);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Fetches the watcher information for this issue.
|
|
230
|
+
*
|
|
231
|
+
* `GET /rest/api/latest/issue/{issueIdOrKey}/watchers`
|
|
232
|
+
*
|
|
233
|
+
* @returns The watchers object
|
|
234
|
+
*/
|
|
235
|
+
async watchers() {
|
|
236
|
+
return this.request(`${this.basePath}/watchers`);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// src/resources/ProjectResource.ts
|
|
241
|
+
var ProjectResource = class {
|
|
242
|
+
/** @internal */
|
|
243
|
+
constructor(request, projectIdOrKey) {
|
|
244
|
+
this.request = request;
|
|
245
|
+
this.basePath = `/project/${projectIdOrKey}`;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Allows the resource to be awaited directly, resolving with the project.
|
|
249
|
+
* Delegates to {@link ProjectResource.get}.
|
|
250
|
+
*/
|
|
251
|
+
then(onfulfilled, onrejected) {
|
|
252
|
+
return this.get().then(onfulfilled, onrejected);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Fetches the project details.
|
|
256
|
+
*
|
|
257
|
+
* `GET /rest/api/latest/project/{projectIdOrKey}`
|
|
258
|
+
*
|
|
259
|
+
* @param params - Optional: `expand`
|
|
260
|
+
* @returns The project object
|
|
261
|
+
*/
|
|
262
|
+
async get(params) {
|
|
263
|
+
return this.request(
|
|
264
|
+
this.basePath,
|
|
265
|
+
params
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Fetches the components defined in this project.
|
|
270
|
+
*
|
|
271
|
+
* `GET /rest/api/latest/project/{projectIdOrKey}/components`
|
|
272
|
+
*
|
|
273
|
+
* @returns An array of project components
|
|
274
|
+
*/
|
|
275
|
+
async components() {
|
|
276
|
+
return this.request(`${this.basePath}/components`);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Fetches the versions defined in this project.
|
|
280
|
+
*
|
|
281
|
+
* `GET /rest/api/latest/project/{projectIdOrKey}/versions`
|
|
282
|
+
*
|
|
283
|
+
* @param expand - Optional: `'operations'`
|
|
284
|
+
* @returns An array of project versions
|
|
285
|
+
*/
|
|
286
|
+
async versions(expand) {
|
|
287
|
+
return this.request(
|
|
288
|
+
`${this.basePath}/versions`,
|
|
289
|
+
expand !== void 0 ? { expand } : void 0
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Fetches the statuses available in this project, grouped by issue type.
|
|
294
|
+
*
|
|
295
|
+
* `GET /rest/api/latest/project/{projectIdOrKey}/statuses`
|
|
296
|
+
*
|
|
297
|
+
* @returns An array of issue type → statuses mappings
|
|
298
|
+
*/
|
|
299
|
+
async statuses() {
|
|
300
|
+
return this.request(`${this.basePath}/statuses`);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Fetches the roles defined in this project.
|
|
304
|
+
*
|
|
305
|
+
* `GET /rest/api/latest/project/{projectIdOrKey}/role`
|
|
306
|
+
*
|
|
307
|
+
* @returns A record mapping role names to their resource URLs
|
|
308
|
+
*/
|
|
309
|
+
async roles() {
|
|
310
|
+
return this.request(`${this.basePath}/role`);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Fetches a single role by ID, including the list of actors.
|
|
314
|
+
*
|
|
315
|
+
* `GET /rest/api/latest/project/{projectIdOrKey}/role/{id}`
|
|
316
|
+
*
|
|
317
|
+
* @param roleId - The numeric role ID
|
|
318
|
+
* @returns The project role object with actors
|
|
319
|
+
*/
|
|
320
|
+
async role(roleId) {
|
|
321
|
+
return this.request(`${this.basePath}/role/${roleId}`);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// src/resources/SprintResource.ts
|
|
326
|
+
var SprintResource = class {
|
|
327
|
+
/** @internal */
|
|
328
|
+
constructor(request, agileApiPath, sprintId) {
|
|
329
|
+
this.request = request;
|
|
330
|
+
this.agileApiPath = agileApiPath;
|
|
331
|
+
this.basePath = `/sprint/${sprintId}`;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Allows the resource to be awaited directly, resolving with the sprint.
|
|
335
|
+
* Delegates to {@link SprintResource.get}.
|
|
336
|
+
*/
|
|
337
|
+
then(onfulfilled, onrejected) {
|
|
338
|
+
return this.get().then(onfulfilled, onrejected);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Fetches the sprint details.
|
|
342
|
+
*
|
|
343
|
+
* `GET /rest/agile/latest/sprint/{sprintId}`
|
|
344
|
+
*
|
|
345
|
+
* @returns The sprint object
|
|
346
|
+
*/
|
|
347
|
+
async get() {
|
|
348
|
+
return this.request(this.basePath, void 0, { apiPath: this.agileApiPath });
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Fetches issues in this sprint.
|
|
352
|
+
*
|
|
353
|
+
* `GET /rest/agile/latest/sprint/{sprintId}/issue`
|
|
354
|
+
*
|
|
355
|
+
* @param params - Optional: `startAt`, `maxResults`, `jql`, `fields`, `expand`
|
|
356
|
+
* @returns A search response containing the sprint's issues
|
|
357
|
+
*/
|
|
358
|
+
async issues(params) {
|
|
359
|
+
return this.request(
|
|
360
|
+
`${this.basePath}/issue`,
|
|
361
|
+
params,
|
|
362
|
+
{ apiPath: this.agileApiPath }
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// src/resources/BoardResource.ts
|
|
368
|
+
var BoardResource = class {
|
|
369
|
+
/** @internal */
|
|
370
|
+
constructor(request, agileApiPath, boardId) {
|
|
371
|
+
this.request = request;
|
|
372
|
+
this.agileApiPath = agileApiPath;
|
|
373
|
+
this.basePath = `/board/${boardId}`;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Allows the resource to be awaited directly, resolving with the board.
|
|
377
|
+
* Delegates to {@link BoardResource.get}.
|
|
378
|
+
*/
|
|
379
|
+
then(onfulfilled, onrejected) {
|
|
380
|
+
return this.get().then(onfulfilled, onrejected);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Fetches the board details.
|
|
384
|
+
*
|
|
385
|
+
* `GET /rest/agile/latest/board/{boardId}`
|
|
386
|
+
*
|
|
387
|
+
* @returns The board object
|
|
388
|
+
*/
|
|
389
|
+
async get() {
|
|
390
|
+
return this.request(this.basePath, void 0, { apiPath: this.agileApiPath });
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Fetches sprints for this board.
|
|
394
|
+
*
|
|
395
|
+
* `GET /rest/agile/latest/board/{boardId}/sprint`
|
|
396
|
+
*
|
|
397
|
+
* @param params - Optional: `startAt`, `maxResults`, `state`
|
|
398
|
+
* @returns A paged response of sprints
|
|
399
|
+
*/
|
|
400
|
+
async sprints(params) {
|
|
401
|
+
return this.request(
|
|
402
|
+
`${this.basePath}/sprint`,
|
|
403
|
+
params,
|
|
404
|
+
{ apiPath: this.agileApiPath }
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Fetches all issues on this board (across all sprints and the backlog).
|
|
409
|
+
*
|
|
410
|
+
* `GET /rest/agile/latest/board/{boardId}/issue`
|
|
411
|
+
*
|
|
412
|
+
* @param params - Optional: `startAt`, `maxResults`, `jql`, `fields`, `expand`
|
|
413
|
+
* @returns A search response containing the board's issues
|
|
414
|
+
*/
|
|
415
|
+
async issues(params) {
|
|
416
|
+
return this.request(
|
|
417
|
+
`${this.basePath}/issue`,
|
|
418
|
+
params,
|
|
419
|
+
{ apiPath: this.agileApiPath }
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Fetches the backlog issues for this board.
|
|
424
|
+
*
|
|
425
|
+
* `GET /rest/agile/latest/board/{boardId}/backlog`
|
|
426
|
+
*
|
|
427
|
+
* @param params - Optional: `startAt`, `maxResults`, `jql`, `fields`, `expand`
|
|
428
|
+
* @returns A search response containing the backlog issues
|
|
429
|
+
*/
|
|
430
|
+
async backlog(params) {
|
|
431
|
+
return this.request(
|
|
432
|
+
`${this.basePath}/backlog`,
|
|
433
|
+
params,
|
|
434
|
+
{ apiPath: this.agileApiPath }
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Returns a {@link SprintResource} for a given sprint ID.
|
|
439
|
+
*
|
|
440
|
+
* The returned resource can be awaited directly to fetch sprint info,
|
|
441
|
+
* or chained to access sprint issues.
|
|
442
|
+
*
|
|
443
|
+
* @param sprintId - The numeric sprint ID
|
|
444
|
+
* @returns A chainable sprint resource
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* ```typescript
|
|
448
|
+
* const sprint = await jiraClient.board(42).sprint(10);
|
|
449
|
+
* const sprintIssues = await jiraClient.board(42).sprint(10).issues();
|
|
450
|
+
* ```
|
|
451
|
+
*/
|
|
452
|
+
sprint(sprintId) {
|
|
453
|
+
return new SprintResource(this.request, this.agileApiPath, sprintId);
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// src/JiraClient.ts
|
|
458
|
+
var JiraClient = class {
|
|
459
|
+
/**
|
|
460
|
+
* @param options - Connection and authentication options
|
|
461
|
+
* @throws {TypeError} If `apiUrl` is not a valid URL
|
|
462
|
+
*/
|
|
463
|
+
constructor({ apiUrl, apiPath = "rest/api/latest", agileApiPath = "rest/agile/latest", user, token }) {
|
|
464
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
465
|
+
this.security = new Security(apiUrl, user, token);
|
|
466
|
+
this.apiPath = apiPath.replace(/^\/|\/$/g, "");
|
|
467
|
+
this.agileApiPath = agileApiPath.replace(/^\/|\/$/g, "");
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Subscribes to a client event.
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* ```typescript
|
|
474
|
+
* jira.on('request', (event) => {
|
|
475
|
+
* console.log(`${event.method} ${event.url} — ${event.durationMs}ms`);
|
|
476
|
+
* if (event.error) console.error('Request failed:', event.error);
|
|
477
|
+
* });
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
on(event, callback) {
|
|
481
|
+
const callbacks = this.listeners.get(event) ?? [];
|
|
482
|
+
callbacks.push(callback);
|
|
483
|
+
this.listeners.set(event, callbacks);
|
|
484
|
+
return this;
|
|
485
|
+
}
|
|
486
|
+
emit(event, payload) {
|
|
487
|
+
const callbacks = this.listeners.get(event) ?? [];
|
|
488
|
+
for (const cb of callbacks) {
|
|
489
|
+
cb(payload);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Performs an authenticated GET request to the Jira REST API.
|
|
494
|
+
* @internal
|
|
495
|
+
*/
|
|
496
|
+
async request(path, params, options) {
|
|
497
|
+
const apiPath = options?.apiPath ?? this.apiPath;
|
|
498
|
+
const base = `${this.security.getApiUrl()}/${apiPath}${path}`;
|
|
499
|
+
const url = buildUrl(base, params);
|
|
500
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
501
|
+
let statusCode;
|
|
502
|
+
try {
|
|
503
|
+
const response = await fetch(url, { headers: this.security.getHeaders() });
|
|
504
|
+
statusCode = response.status;
|
|
505
|
+
if (!response.ok) {
|
|
506
|
+
throw new JiraApiError(response.status, response.statusText);
|
|
507
|
+
}
|
|
508
|
+
const data = await response.json();
|
|
509
|
+
this.emit("request", { url, method: "GET", startedAt, finishedAt: /* @__PURE__ */ new Date(), durationMs: Date.now() - startedAt.getTime(), statusCode });
|
|
510
|
+
return data;
|
|
511
|
+
} catch (err) {
|
|
512
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
513
|
+
this.emit("request", { url, method: "GET", startedAt, finishedAt, durationMs: finishedAt.getTime() - startedAt.getTime(), statusCode, error: err instanceof Error ? err : new Error(String(err)) });
|
|
514
|
+
throw err;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async requestPost(path, body, options) {
|
|
518
|
+
const apiPath = options?.apiPath ?? this.apiPath;
|
|
519
|
+
const url = `${this.security.getApiUrl()}/${apiPath}${path}`;
|
|
520
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
521
|
+
let statusCode;
|
|
522
|
+
try {
|
|
523
|
+
const response = await fetch(url, {
|
|
524
|
+
method: "POST",
|
|
525
|
+
headers: this.security.getHeaders(),
|
|
526
|
+
body: JSON.stringify(body)
|
|
527
|
+
});
|
|
528
|
+
statusCode = response.status;
|
|
529
|
+
if (!response.ok) {
|
|
530
|
+
throw new JiraApiError(response.status, response.statusText);
|
|
531
|
+
}
|
|
532
|
+
const data = await response.json();
|
|
533
|
+
this.emit("request", { url, method: "POST", startedAt, finishedAt: /* @__PURE__ */ new Date(), durationMs: Date.now() - startedAt.getTime(), statusCode });
|
|
534
|
+
return data;
|
|
535
|
+
} catch (err) {
|
|
536
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
537
|
+
this.emit("request", { url, method: "POST", startedAt, finishedAt, durationMs: finishedAt.getTime() - startedAt.getTime(), statusCode, error: err instanceof Error ? err : new Error(String(err)) });
|
|
538
|
+
throw err;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// ─── Issue ───────────────────────────────────────────────────────────────────
|
|
542
|
+
/**
|
|
543
|
+
* Returns an {@link IssueResource} for a given issue key or ID, providing access
|
|
544
|
+
* to issue data and sub-resources (comments, changelog, transitions, etc.).
|
|
545
|
+
*
|
|
546
|
+
* The returned resource can be awaited directly to fetch the issue,
|
|
547
|
+
* or chained to access nested resources.
|
|
548
|
+
*
|
|
549
|
+
* @param issueIdOrKey - The issue key (e.g., `'PROJ-42'`) or numeric ID
|
|
550
|
+
* @returns A chainable issue resource
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* ```typescript
|
|
554
|
+
* const issue = await jira.issue('PROJ-42');
|
|
555
|
+
* const comments = await jira.issue('PROJ-42').comments();
|
|
556
|
+
* const changelog = await jira.issue('PROJ-42').changelog();
|
|
557
|
+
* const transitions = await jira.issue('PROJ-42').transitions();
|
|
558
|
+
* ```
|
|
559
|
+
*/
|
|
560
|
+
issue(issueIdOrKey) {
|
|
561
|
+
const requestFn = (path, params, options) => this.request(path, params, options);
|
|
562
|
+
return new IssueResource(requestFn, issueIdOrKey);
|
|
563
|
+
}
|
|
564
|
+
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
565
|
+
/**
|
|
566
|
+
* Searches for issues using JQL.
|
|
567
|
+
*
|
|
568
|
+
* `GET /rest/api/latest/search`
|
|
569
|
+
*
|
|
570
|
+
* @param params - Optional: `jql`, `startAt`, `maxResults`, `fields`, `expand`, `validateQuery`
|
|
571
|
+
* @returns A search response containing matching issues
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* ```typescript
|
|
575
|
+
* const results = await jira.search({
|
|
576
|
+
* jql: 'project = PROJ AND status = Open ORDER BY created DESC',
|
|
577
|
+
* maxResults: 50,
|
|
578
|
+
* fields: 'summary,status,assignee,priority',
|
|
579
|
+
* });
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
async search(params) {
|
|
583
|
+
return this.request(
|
|
584
|
+
"/search",
|
|
585
|
+
params
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
// ─── Projects ────────────────────────────────────────────────────────────────
|
|
589
|
+
/**
|
|
590
|
+
* Fetches all projects accessible to the authenticated user.
|
|
591
|
+
*
|
|
592
|
+
* `GET /rest/api/latest/project`
|
|
593
|
+
*
|
|
594
|
+
* @param params - Optional: `expand`, `recent`
|
|
595
|
+
* @returns An array of projects
|
|
596
|
+
*/
|
|
597
|
+
async projects(params) {
|
|
598
|
+
return this.request(
|
|
599
|
+
"/project",
|
|
600
|
+
params
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Returns a {@link ProjectResource} for a given project key or ID.
|
|
605
|
+
*
|
|
606
|
+
* The returned resource can be awaited directly to fetch project info,
|
|
607
|
+
* or chained to access nested resources.
|
|
608
|
+
*
|
|
609
|
+
* @param projectIdOrKey - The project key (e.g., `'PROJ'`) or numeric ID
|
|
610
|
+
* @returns A chainable project resource
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* ```typescript
|
|
614
|
+
* const project = await jira.project('PROJ');
|
|
615
|
+
* const components = await jira.project('PROJ').components();
|
|
616
|
+
* const versions = await jira.project('PROJ').versions();
|
|
617
|
+
* const statuses = await jira.project('PROJ').statuses();
|
|
618
|
+
* ```
|
|
619
|
+
*/
|
|
620
|
+
project(projectIdOrKey) {
|
|
621
|
+
const requestFn = (path, params, options) => this.request(path, params, options);
|
|
622
|
+
return new ProjectResource(requestFn, projectIdOrKey);
|
|
623
|
+
}
|
|
624
|
+
// ─── Users ───────────────────────────────────────────────────────────────────
|
|
625
|
+
/**
|
|
626
|
+
* Searches for users.
|
|
627
|
+
*
|
|
628
|
+
* `GET /rest/api/latest/user/search`
|
|
629
|
+
*
|
|
630
|
+
* @param params - Optional: `username`, `startAt`, `maxResults`
|
|
631
|
+
* @returns An array of users
|
|
632
|
+
*/
|
|
633
|
+
async users(params) {
|
|
634
|
+
return this.request(
|
|
635
|
+
"/user/search",
|
|
636
|
+
params
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Fetches a single user by username or key.
|
|
641
|
+
*
|
|
642
|
+
* `GET /rest/api/latest/user`
|
|
643
|
+
*
|
|
644
|
+
* @param username - The username (login name) to look up
|
|
645
|
+
* @returns The user object
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* ```typescript
|
|
649
|
+
* const user = await jira.user('pilmee');
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
async user(username) {
|
|
653
|
+
return this.request("/user", { username });
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Fetches the currently authenticated user.
|
|
657
|
+
*
|
|
658
|
+
* `GET /rest/api/latest/myself`
|
|
659
|
+
*
|
|
660
|
+
* @returns The authenticated user object
|
|
661
|
+
*
|
|
662
|
+
* @example
|
|
663
|
+
* ```typescript
|
|
664
|
+
* const me = await jira.currentUser();
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
async currentUser() {
|
|
668
|
+
return this.request("/myself");
|
|
669
|
+
}
|
|
670
|
+
// ─── Boards (Agile) ──────────────────────────────────────────────────────────
|
|
671
|
+
/**
|
|
672
|
+
* Fetches all boards accessible to the authenticated user.
|
|
673
|
+
*
|
|
674
|
+
* `GET /rest/agile/latest/board`
|
|
675
|
+
*
|
|
676
|
+
* @param params - Optional: `startAt`, `maxResults`, `type`, `name`, `projectKeyOrId`
|
|
677
|
+
* @returns A paged response of boards
|
|
678
|
+
*/
|
|
679
|
+
async boards(params) {
|
|
680
|
+
return this.request(
|
|
681
|
+
"/board",
|
|
682
|
+
params,
|
|
683
|
+
{ apiPath: this.agileApiPath }
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Returns a {@link BoardResource} for a given board ID.
|
|
688
|
+
*
|
|
689
|
+
* The returned resource can be awaited directly to fetch board info,
|
|
690
|
+
* or chained to access nested resources (sprints, issues, backlog).
|
|
691
|
+
*
|
|
692
|
+
* @param boardId - The numeric board ID
|
|
693
|
+
* @returns A chainable board resource
|
|
694
|
+
*
|
|
695
|
+
* @example
|
|
696
|
+
* ```typescript
|
|
697
|
+
* const board = await jira.board(42);
|
|
698
|
+
* const sprints = await jira.board(42).sprints({ state: 'active' });
|
|
699
|
+
* const backlog = await jira.board(42).backlog({ maxResults: 50 });
|
|
700
|
+
* const sprintIssues = await jira.board(42).sprint(10).issues();
|
|
701
|
+
* ```
|
|
702
|
+
*/
|
|
703
|
+
board(boardId) {
|
|
704
|
+
const requestFn = (path, params, options) => this.request(path, params, options);
|
|
705
|
+
return new BoardResource(requestFn, this.agileApiPath, boardId);
|
|
706
|
+
}
|
|
707
|
+
// ─── Metadata ────────────────────────────────────────────────────────────────
|
|
708
|
+
/**
|
|
709
|
+
* Fetches all issue types available to the authenticated user.
|
|
710
|
+
*
|
|
711
|
+
* `GET /rest/api/latest/issuetype`
|
|
712
|
+
*
|
|
713
|
+
* @returns An array of issue types
|
|
714
|
+
*/
|
|
715
|
+
async issuetypes() {
|
|
716
|
+
return this.request("/issuetype");
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Fetches a single issue type by ID.
|
|
720
|
+
*
|
|
721
|
+
* `GET /rest/api/latest/issuetype/{id}`
|
|
722
|
+
*
|
|
723
|
+
* @param id - The issue type ID
|
|
724
|
+
* @returns The issue type object
|
|
725
|
+
*/
|
|
726
|
+
async issuetype(id) {
|
|
727
|
+
return this.request(`/issuetype/${id}`);
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Fetches all priorities.
|
|
731
|
+
*
|
|
732
|
+
* `GET /rest/api/latest/priority`
|
|
733
|
+
*
|
|
734
|
+
* @returns An array of priorities
|
|
735
|
+
*/
|
|
736
|
+
async priorities() {
|
|
737
|
+
return this.request("/priority");
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Fetches a single priority by ID.
|
|
741
|
+
*
|
|
742
|
+
* `GET /rest/api/latest/priority/{id}`
|
|
743
|
+
*
|
|
744
|
+
* @param id - The priority ID
|
|
745
|
+
* @returns The priority object
|
|
746
|
+
*/
|
|
747
|
+
async priority(id) {
|
|
748
|
+
return this.request(`/priority/${id}`);
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Fetches all statuses.
|
|
752
|
+
*
|
|
753
|
+
* `GET /rest/api/latest/status`
|
|
754
|
+
*
|
|
755
|
+
* @returns An array of statuses
|
|
756
|
+
*/
|
|
757
|
+
async statuses() {
|
|
758
|
+
return this.request("/status");
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Fetches a single status by ID or name.
|
|
762
|
+
*
|
|
763
|
+
* `GET /rest/api/latest/status/{idOrName}`
|
|
764
|
+
*
|
|
765
|
+
* @param idOrName - The status ID or name
|
|
766
|
+
* @returns The status object
|
|
767
|
+
*/
|
|
768
|
+
async status(idOrName) {
|
|
769
|
+
return this.request(`/status/${encodeURIComponent(idOrName)}`);
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Fetches all issue fields (system and custom).
|
|
773
|
+
*
|
|
774
|
+
* `GET /rest/api/latest/field`
|
|
775
|
+
*
|
|
776
|
+
* @returns An array of fields
|
|
777
|
+
*/
|
|
778
|
+
async fields() {
|
|
779
|
+
return this.request("/field");
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Fetches all issue link types.
|
|
783
|
+
*
|
|
784
|
+
* `GET /rest/api/latest/issueLinkType`
|
|
785
|
+
*
|
|
786
|
+
* @returns An object with the list of issue link types
|
|
787
|
+
*/
|
|
788
|
+
async issueLinkTypes() {
|
|
789
|
+
return this.request("/issueLinkType");
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Fetches the authenticated user's favourite filters.
|
|
793
|
+
*
|
|
794
|
+
* `GET /rest/api/latest/filter/favourite`
|
|
795
|
+
*
|
|
796
|
+
* @returns An array of filters
|
|
797
|
+
*/
|
|
798
|
+
async favouriteFilters() {
|
|
799
|
+
return this.request("/filter/favourite");
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Fetches a single filter by ID.
|
|
803
|
+
*
|
|
804
|
+
* `GET /rest/api/latest/filter/{id}`
|
|
805
|
+
*
|
|
806
|
+
* @param filterId - The numeric filter ID
|
|
807
|
+
* @returns The filter object
|
|
808
|
+
*/
|
|
809
|
+
async filter(filterId) {
|
|
810
|
+
return this.request(`/filter/${filterId}`);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Fetches issues using a `POST` search, which supports larger JQL queries
|
|
814
|
+
* and additional options such as specifying the fields list as an array.
|
|
815
|
+
*
|
|
816
|
+
* `POST /rest/api/latest/search`
|
|
817
|
+
*
|
|
818
|
+
* @param body - Search request body
|
|
819
|
+
* @returns A search response containing matching issues
|
|
820
|
+
*
|
|
821
|
+
* @example
|
|
822
|
+
* ```typescript
|
|
823
|
+
* const results = await jira.searchPost({
|
|
824
|
+
* jql: 'project = PROJ AND status = Open',
|
|
825
|
+
* maxResults: 100,
|
|
826
|
+
* fields: ['summary', 'status', 'assignee'],
|
|
827
|
+
* });
|
|
828
|
+
* ```
|
|
829
|
+
*/
|
|
830
|
+
async searchPost(body) {
|
|
831
|
+
return this.requestPost("/search", body);
|
|
832
|
+
}
|
|
833
|
+
// ─── Components & Versions ───────────────────────────────────────────────────
|
|
834
|
+
/**
|
|
835
|
+
* Fetches a single component by ID.
|
|
836
|
+
*
|
|
837
|
+
* `GET /rest/api/latest/component/{id}`
|
|
838
|
+
*
|
|
839
|
+
* @param componentId - The component ID
|
|
840
|
+
* @returns The component object
|
|
841
|
+
*/
|
|
842
|
+
async component(componentId) {
|
|
843
|
+
return this.request(`/component/${componentId}`);
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Fetches a single version by ID.
|
|
847
|
+
*
|
|
848
|
+
* `GET /rest/api/latest/version/{id}`
|
|
849
|
+
*
|
|
850
|
+
* @param versionId - The version ID
|
|
851
|
+
* @returns The version object
|
|
852
|
+
*/
|
|
853
|
+
async version(versionId) {
|
|
854
|
+
return this.request(`/version/${versionId}`);
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Fetches the related issue counts for a version.
|
|
858
|
+
*
|
|
859
|
+
* `GET /rest/api/latest/version/{id}/relatedIssueCounts`
|
|
860
|
+
*
|
|
861
|
+
* @param versionId - The version ID
|
|
862
|
+
* @returns Issue counts by type
|
|
863
|
+
*/
|
|
864
|
+
async versionIssueCounts(versionId) {
|
|
865
|
+
return this.request(`/version/${versionId}/relatedIssueCounts`);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Fetches the number of unresolved issues for a version.
|
|
869
|
+
*
|
|
870
|
+
* `GET /rest/api/latest/version/{id}/unresolvedIssueCount`
|
|
871
|
+
*
|
|
872
|
+
* @param versionId - The version ID
|
|
873
|
+
* @returns The unresolved issue count
|
|
874
|
+
*/
|
|
875
|
+
async versionUnresolvedIssueCount(versionId) {
|
|
876
|
+
return this.request(`/version/${versionId}/unresolvedIssueCount`);
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
function buildUrl(base, params) {
|
|
880
|
+
if (!params) return base;
|
|
881
|
+
const entries = Object.entries(params).filter(([, v]) => v !== void 0);
|
|
882
|
+
if (entries.length === 0) return base;
|
|
883
|
+
const search = new URLSearchParams(entries.map(([k, v]) => [k, String(v)]));
|
|
884
|
+
return `${base}?${search.toString()}`;
|
|
885
|
+
}
|
|
886
|
+
//# sourceMappingURL=index.js.map
|