@evref-bl/dev-nexus 0.1.0-alpha.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.
Files changed (100) hide show
  1. package/README.md +677 -0
  2. package/dist/browserOpener.d.ts +9 -0
  3. package/dist/browserOpener.js +47 -0
  4. package/dist/cli.d.ts +18 -0
  5. package/dist/cli.js +2374 -0
  6. package/dist/gitWorktreeService.d.ts +57 -0
  7. package/dist/gitWorktreeService.js +157 -0
  8. package/dist/index.d.ts +47 -0
  9. package/dist/index.js +47 -0
  10. package/dist/nexusAgentMcpConfig.d.ts +30 -0
  11. package/dist/nexusAgentMcpConfig.js +228 -0
  12. package/dist/nexusAutomation.d.ts +103 -0
  13. package/dist/nexusAutomation.js +390 -0
  14. package/dist/nexusAutomationAgentLaunch.d.ts +148 -0
  15. package/dist/nexusAutomationAgentLaunch.js +855 -0
  16. package/dist/nexusAutomationAgentProfile.d.ts +39 -0
  17. package/dist/nexusAutomationAgentProfile.js +103 -0
  18. package/dist/nexusAutomationAgentSurface.d.ts +62 -0
  19. package/dist/nexusAutomationAgentSurface.js +90 -0
  20. package/dist/nexusAutomationCommandExecutor.d.ts +29 -0
  21. package/dist/nexusAutomationCommandExecutor.js +251 -0
  22. package/dist/nexusAutomationConfig.d.ts +114 -0
  23. package/dist/nexusAutomationConfig.js +547 -0
  24. package/dist/nexusAutomationEnqueue.d.ts +37 -0
  25. package/dist/nexusAutomationEnqueue.js +128 -0
  26. package/dist/nexusAutomationRunOnce.d.ts +91 -0
  27. package/dist/nexusAutomationRunOnce.js +586 -0
  28. package/dist/nexusAutomationScheduler.d.ts +50 -0
  29. package/dist/nexusAutomationScheduler.js +196 -0
  30. package/dist/nexusAutomationStatus.d.ts +55 -0
  31. package/dist/nexusAutomationStatus.js +462 -0
  32. package/dist/nexusAutomationTarget.d.ts +19 -0
  33. package/dist/nexusAutomationTarget.js +33 -0
  34. package/dist/nexusAutomationTargetCycle.d.ts +90 -0
  35. package/dist/nexusAutomationTargetCycle.js +282 -0
  36. package/dist/nexusAutomationTargetReport.d.ts +136 -0
  37. package/dist/nexusAutomationTargetReport.js +504 -0
  38. package/dist/nexusAutomationWorktreeSetup.d.ts +89 -0
  39. package/dist/nexusAutomationWorktreeSetup.js +661 -0
  40. package/dist/nexusCoordination.d.ts +198 -0
  41. package/dist/nexusCoordination.js +1018 -0
  42. package/dist/nexusExtension.d.ts +31 -0
  43. package/dist/nexusExtension.js +1 -0
  44. package/dist/nexusHomeConfig.d.ts +38 -0
  45. package/dist/nexusHomeConfig.js +133 -0
  46. package/dist/nexusMcpServer.d.ts +31 -0
  47. package/dist/nexusMcpServer.js +1036 -0
  48. package/dist/nexusPluginCapabilities.d.ts +197 -0
  49. package/dist/nexusPluginCapabilities.js +201 -0
  50. package/dist/nexusProjectConfig.d.ts +95 -0
  51. package/dist/nexusProjectConfig.js +880 -0
  52. package/dist/nexusProjectHomeService.d.ts +121 -0
  53. package/dist/nexusProjectHomeService.js +171 -0
  54. package/dist/nexusProjectLifecycle.d.ts +62 -0
  55. package/dist/nexusProjectLifecycle.js +205 -0
  56. package/dist/nexusProjectOperations.d.ts +101 -0
  57. package/dist/nexusProjectOperations.js +296 -0
  58. package/dist/nexusProjectRegistry.d.ts +42 -0
  59. package/dist/nexusProjectRegistry.js +91 -0
  60. package/dist/nexusProjectScaffold.d.ts +25 -0
  61. package/dist/nexusProjectScaffold.js +61 -0
  62. package/dist/nexusProjectTemplate.d.ts +34 -0
  63. package/dist/nexusProjectTemplate.js +354 -0
  64. package/dist/nexusSkills.d.ts +134 -0
  65. package/dist/nexusSkills.js +647 -0
  66. package/dist/nexusWorkerContextBundle.d.ts +142 -0
  67. package/dist/nexusWorkerContextBundle.js +375 -0
  68. package/dist/processSupervisor.d.ts +89 -0
  69. package/dist/processSupervisor.js +440 -0
  70. package/dist/vibeKanbanApi.d.ts +11 -0
  71. package/dist/vibeKanbanApi.js +14 -0
  72. package/dist/vibeKanbanAuth.d.ts +25 -0
  73. package/dist/vibeKanbanAuth.js +101 -0
  74. package/dist/vibeKanbanBoardAdapter.d.ts +36 -0
  75. package/dist/vibeKanbanBoardAdapter.js +196 -0
  76. package/dist/vibeKanbanMcpConfig.d.ts +36 -0
  77. package/dist/vibeKanbanMcpConfig.js +191 -0
  78. package/dist/vibeKanbanProjectAdapter.d.ts +39 -0
  79. package/dist/vibeKanbanProjectAdapter.js +113 -0
  80. package/dist/vibeKanbanWorkspaceSetup.d.ts +1 -0
  81. package/dist/vibeKanbanWorkspaceSetup.js +96 -0
  82. package/dist/workItemService.d.ts +60 -0
  83. package/dist/workItemService.js +163 -0
  84. package/dist/workTrackingGitHubProvider.d.ts +71 -0
  85. package/dist/workTrackingGitHubProvider.js +663 -0
  86. package/dist/workTrackingGitLabProvider.d.ts +62 -0
  87. package/dist/workTrackingGitLabProvider.js +523 -0
  88. package/dist/workTrackingJiraProvider.d.ts +67 -0
  89. package/dist/workTrackingJiraProvider.js +652 -0
  90. package/dist/workTrackingLocalProvider.d.ts +49 -0
  91. package/dist/workTrackingLocalProvider.js +463 -0
  92. package/dist/workTrackingProviderService.d.ts +21 -0
  93. package/dist/workTrackingProviderService.js +117 -0
  94. package/dist/workTrackingTypes.d.ts +202 -0
  95. package/dist/workTrackingTypes.js +1 -0
  96. package/dist/workTrackingVibeProvider.d.ts +35 -0
  97. package/dist/workTrackingVibeProvider.js +119 -0
  98. package/dist/worktreeExecutionMetadata.d.ts +76 -0
  99. package/dist/worktreeExecutionMetadata.js +239 -0
  100. package/package.json +37 -0
@@ -0,0 +1,62 @@
1
+ import type { CreateWorkItemInput, GitLabWorkTrackingConfig, TrackerCapabilities, WorkComment, WorkItem, WorkItemPatch, WorkItemQuery, WorkItemRef, WorkStatus, WorkTrackerProvider } from "./workTrackingTypes.js";
2
+ export declare const defaultGitLabApiBaseUrl = "https://gitlab.com/api/v4";
3
+ export declare const gitLabStatusLabelPrefix = "status:";
4
+ export interface GitLabWorkTrackerProviderOptions {
5
+ config: GitLabWorkTrackingConfig;
6
+ token?: string | null;
7
+ fetch?: typeof fetch;
8
+ apiBaseUrl?: string | null;
9
+ env?: Record<string, string | undefined>;
10
+ credentialRunner?: GitLabCredentialRunner | false;
11
+ credentialInteractive?: boolean;
12
+ }
13
+ export interface GitLabCredentialCommandResult {
14
+ status: number | null;
15
+ stdout: string;
16
+ stderr: string;
17
+ error?: Error;
18
+ }
19
+ export interface GitLabCredentialRequest {
20
+ protocol: "https";
21
+ host: string;
22
+ path?: string;
23
+ }
24
+ export type GitLabCredentialRunner = (request: GitLabCredentialRequest, options: {
25
+ interactive: boolean;
26
+ }) => GitLabCredentialCommandResult;
27
+ export declare class GitLabWorkTrackerProviderError extends Error {
28
+ constructor(message: string);
29
+ }
30
+ export declare const gitLabWorkTrackerCapabilities: TrackerCapabilities;
31
+ export declare function createGitLabWorkTrackerProvider(options: GitLabWorkTrackerProviderOptions): GitLabWorkTrackerProvider;
32
+ export declare class GitLabWorkTrackerProvider implements WorkTrackerProvider {
33
+ readonly provider = "gitlab";
34
+ readonly capabilities: TrackerCapabilities;
35
+ private readonly config;
36
+ private readonly fetchFn;
37
+ private readonly apiBaseUrl;
38
+ private readonly staticPrivateToken?;
39
+ private readonly credentialRunner?;
40
+ private readonly credentialInteractive;
41
+ private credentialHeaders;
42
+ constructor(options: GitLabWorkTrackerProviderOptions);
43
+ createWorkItem(input: CreateWorkItemInput): Promise<WorkItem>;
44
+ listWorkItems(query?: WorkItemQuery): Promise<WorkItem[]>;
45
+ getWorkItem(ref: WorkItemRef): Promise<WorkItem>;
46
+ updateWorkItem(ref: WorkItemRef, patch: WorkItemPatch): Promise<WorkItem>;
47
+ addComment(ref: WorkItemRef, body: string): Promise<WorkComment>;
48
+ setStatus(ref: WorkItemRef, status: WorkStatus): Promise<WorkItem>;
49
+ private getIssue;
50
+ private issuePath;
51
+ private requestJson;
52
+ private issueToWorkItem;
53
+ private noteToWorkComment;
54
+ private issueExternalRef;
55
+ private authorizationHeaders;
56
+ }
57
+ export declare function normalizeGitLabApiBaseUrl(hostOrApiBaseUrl?: string | null): string;
58
+ export declare function gitLabCredentialRequest(config: Pick<GitLabWorkTrackingConfig, "host" | "repository">): GitLabCredentialRequest;
59
+ export declare function normalizeGitLabCredentialHost(hostOrApiBaseUrl?: string | null): string;
60
+ export declare function defaultGitLabCredentialRunner(request: GitLabCredentialRequest, options: {
61
+ interactive: boolean;
62
+ }): GitLabCredentialCommandResult;
@@ -0,0 +1,523 @@
1
+ import { spawnSync } from "node:child_process";
2
+ export const defaultGitLabApiBaseUrl = "https://gitlab.com/api/v4";
3
+ export const gitLabStatusLabelPrefix = "status:";
4
+ const openStatuses = new Set([
5
+ "todo",
6
+ "ready",
7
+ "in_progress",
8
+ "blocked",
9
+ ]);
10
+ const closedStatuses = new Set(["done", "wont_do"]);
11
+ const workStatuses = new Set([
12
+ ...openStatuses,
13
+ ...closedStatuses,
14
+ ]);
15
+ export class GitLabWorkTrackerProviderError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = "GitLabWorkTrackerProviderError";
19
+ }
20
+ }
21
+ export const gitLabWorkTrackerCapabilities = {
22
+ createItem: true,
23
+ listItems: true,
24
+ getItem: true,
25
+ updateItem: true,
26
+ comment: true,
27
+ labels: true,
28
+ assignees: true,
29
+ milestones: true,
30
+ board: false,
31
+ boardStatus: false,
32
+ draftItems: false,
33
+ webhooks: false,
34
+ };
35
+ export function createGitLabWorkTrackerProvider(options) {
36
+ return new GitLabWorkTrackerProvider(options);
37
+ }
38
+ export class GitLabWorkTrackerProvider {
39
+ provider = "gitlab";
40
+ capabilities = gitLabWorkTrackerCapabilities;
41
+ config;
42
+ fetchFn;
43
+ apiBaseUrl;
44
+ staticPrivateToken;
45
+ credentialRunner;
46
+ credentialInteractive;
47
+ credentialHeaders;
48
+ constructor(options) {
49
+ this.config = options.config;
50
+ this.fetchFn = options.fetch ?? fetch;
51
+ this.apiBaseUrl = normalizeGitLabApiBaseUrl(options.apiBaseUrl ?? options.config.host);
52
+ this.staticPrivateToken =
53
+ optionalNonEmptyString(options.token, "token") ??
54
+ optionalNonEmptyString((options.env ?? process.env).GITLAB_TOKEN, "GITLAB_TOKEN") ??
55
+ optionalNonEmptyString((options.env ?? process.env).GL_TOKEN, "GL_TOKEN");
56
+ this.credentialRunner =
57
+ options.credentialRunner === false
58
+ ? undefined
59
+ : options.credentialRunner ?? defaultGitLabCredentialRunner;
60
+ this.credentialInteractive = options.credentialInteractive ?? false;
61
+ }
62
+ async createWorkItem(input) {
63
+ const status = input.status ?? "todo";
64
+ assertWorkStatus(status);
65
+ const created = await this.requestJson("POST", this.issuePath(), {
66
+ title: requiredNonEmptyString(input.title, "title"),
67
+ ...(input.description !== undefined
68
+ ? { description: input.description }
69
+ : {}),
70
+ ...requestLabels(labelsWithStatus(input.labels, status)),
71
+ ...requestAssignees(input.assignees),
72
+ ...requestMilestone(input.milestone),
73
+ });
74
+ if (closedStatuses.has(status)) {
75
+ return this.updateWorkItem({ id: String(created.iid) }, { status });
76
+ }
77
+ return this.issueToWorkItem(created);
78
+ }
79
+ async listWorkItems(query = {}) {
80
+ const statuses = normalizeStatusFilter(query.status);
81
+ const state = gitLabStateForQuery(statuses);
82
+ const limit = normalizeLimit(query.limit);
83
+ const params = new URLSearchParams({
84
+ state,
85
+ per_page: String(limit ? Math.min(Math.max(limit, 1), 100) : 100),
86
+ page: "1",
87
+ });
88
+ const labels = normalizeStringArray(query.labels, "labels");
89
+ if (labels.length > 0) {
90
+ params.set("labels", labels.join(","));
91
+ }
92
+ const issues = await this.requestJson("GET", `${this.issuePath()}?${params.toString()}`);
93
+ const assignees = normalizeStringArray(query.assignees, "assignees");
94
+ const search = query.search?.trim().toLowerCase();
95
+ const items = issues
96
+ .map((issue) => this.issueToWorkItem(issue))
97
+ .filter((item) => matchesStatusFilter(item, statuses))
98
+ .filter((item) => matchesStringFilter(item.assignees, assignees))
99
+ .filter((item) => !search || matchesSearch(item, search));
100
+ return limit === undefined ? items : items.slice(0, limit);
101
+ }
102
+ async getWorkItem(ref) {
103
+ return this.issueToWorkItem(await this.getIssue(ref));
104
+ }
105
+ async updateWorkItem(ref, patch) {
106
+ const issueIid = issueIidFromRef(ref);
107
+ const body = {};
108
+ if (patch.title !== undefined) {
109
+ body.title = requiredNonEmptyString(patch.title, "title");
110
+ }
111
+ if (patch.description !== undefined) {
112
+ body.description = patch.description;
113
+ }
114
+ if (patch.assignees !== undefined) {
115
+ Object.assign(body, requestAssignees(patch.assignees));
116
+ }
117
+ if (patch.milestone !== undefined) {
118
+ Object.assign(body, requestMilestone(patch.milestone));
119
+ }
120
+ if (patch.status !== undefined) {
121
+ assertWorkStatus(patch.status);
122
+ body.state_event = closedStatuses.has(patch.status) ? "close" : "reopen";
123
+ }
124
+ if (patch.labels !== undefined || patch.status !== undefined) {
125
+ const baseLabels = patch.labels !== undefined
126
+ ? normalizeStringArray(patch.labels, "labels")
127
+ : labelNames(await this.getIssue(ref));
128
+ Object.assign(body, requestLabels(labelsWithStatus(baseLabels, patch.status)));
129
+ }
130
+ return this.issueToWorkItem(await this.requestJson("PUT", `${this.issuePath()}/${issueIid}`, body));
131
+ }
132
+ async addComment(ref, body) {
133
+ const issueIid = issueIidFromRef(ref);
134
+ const note = await this.requestJson("POST", `${this.issuePath()}/${issueIid}/notes`, {
135
+ body: requiredNonEmptyString(body, "body"),
136
+ });
137
+ return this.noteToWorkComment(note, issueIid);
138
+ }
139
+ async setStatus(ref, status) {
140
+ return this.updateWorkItem(ref, { status });
141
+ }
142
+ async getIssue(ref) {
143
+ const issueIid = issueIidFromRef(ref);
144
+ return this.requestJson("GET", `${this.issuePath()}/${issueIid}`);
145
+ }
146
+ issuePath() {
147
+ return `/projects/${encodePathSegment(this.config.repository.id)}/issues`;
148
+ }
149
+ async requestJson(method, pathAndQuery, body) {
150
+ const url = new URL(pathAndQuery.replace(/^\/+/, ""), `${this.apiBaseUrl}/`);
151
+ const authorizationHeaders = this.authorizationHeaders();
152
+ const headers = {
153
+ Accept: "application/json",
154
+ "User-Agent": "dev-nexus",
155
+ ...authorizationHeaders,
156
+ };
157
+ if (body !== undefined) {
158
+ headers["Content-Type"] = "application/json";
159
+ }
160
+ const response = await this.fetchFn(url, {
161
+ method,
162
+ headers,
163
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
164
+ });
165
+ if (!response.ok) {
166
+ const message = await gitLabErrorMessage(response, method, url);
167
+ throw new GitLabWorkTrackerProviderError(gitLabErrorMessageWithCredentialHint(message, response, authorizationHeaders, this.config));
168
+ }
169
+ return (await response.json());
170
+ }
171
+ issueToWorkItem(issue) {
172
+ return {
173
+ id: `gitlab-${issue.iid}`,
174
+ title: requiredNonEmptyString(issue.title, "issue.title"),
175
+ description: issue.description ?? null,
176
+ status: workStatusFromIssue(issue),
177
+ provider: "gitlab",
178
+ labels: userLabelNames(issue),
179
+ assignees: assigneeUsernames(issue),
180
+ milestone: issue.milestone?.title ?? null,
181
+ createdAt: issue.created_at ?? null,
182
+ updatedAt: issue.updated_at ?? null,
183
+ closedAt: issue.closed_at ?? null,
184
+ webUrl: issue.web_url ?? null,
185
+ externalRef: this.issueExternalRef(issue),
186
+ };
187
+ }
188
+ noteToWorkComment(note, issueIid) {
189
+ return {
190
+ id: `gitlab-note-${note.id}`,
191
+ body: note.body ?? "",
192
+ author: note.author?.username ?? null,
193
+ createdAt: note.created_at ?? null,
194
+ updatedAt: note.updated_at ?? null,
195
+ externalRef: {
196
+ provider: "gitlab",
197
+ host: this.config.host ?? null,
198
+ repositoryId: this.config.repository.id,
199
+ itemId: String(note.id),
200
+ itemNumber: note.noteable_iid ?? issueIid,
201
+ },
202
+ };
203
+ }
204
+ issueExternalRef(issue) {
205
+ return {
206
+ provider: "gitlab",
207
+ host: this.config.host ?? null,
208
+ repositoryId: this.config.repository.id,
209
+ itemId: String(issue.iid),
210
+ itemNumber: issue.iid,
211
+ itemKey: issue.references?.full ?? issue.references?.relative ?? null,
212
+ nodeId: String(issue.id),
213
+ webUrl: issue.web_url ?? null,
214
+ };
215
+ }
216
+ authorizationHeaders() {
217
+ if (this.staticPrivateToken) {
218
+ return { "PRIVATE-TOKEN": this.staticPrivateToken };
219
+ }
220
+ if (this.credentialHeaders !== undefined) {
221
+ return this.credentialHeaders ?? {};
222
+ }
223
+ if (!this.credentialRunner) {
224
+ this.credentialHeaders = null;
225
+ return {};
226
+ }
227
+ const credential = fillGitLabCredential(this.credentialRunner, gitLabCredentialRequest(this.config), { interactive: this.credentialInteractive });
228
+ this.credentialHeaders = credential
229
+ ? authorizationHeadersFromCredential(credential)
230
+ : null;
231
+ return this.credentialHeaders ?? {};
232
+ }
233
+ }
234
+ export function normalizeGitLabApiBaseUrl(hostOrApiBaseUrl) {
235
+ const value = hostOrApiBaseUrl?.trim();
236
+ if (!value || value === "gitlab.com" || value === "https://gitlab.com") {
237
+ return defaultGitLabApiBaseUrl;
238
+ }
239
+ const url = value.startsWith("http://") || value.startsWith("https://")
240
+ ? new URL(value)
241
+ : new URL(`https://${value}`);
242
+ const normalizedPath = url.pathname.replace(/\/+$/, "");
243
+ if (normalizedPath.endsWith("/api/v4")) {
244
+ return `${url.protocol}//${url.host}${normalizedPath}`;
245
+ }
246
+ return `${url.protocol}//${url.host}${normalizedPath}/api/v4`;
247
+ }
248
+ export function gitLabCredentialRequest(config) {
249
+ return {
250
+ protocol: "https",
251
+ host: normalizeGitLabCredentialHost(config.host),
252
+ path: `${config.repository.id}.git`,
253
+ };
254
+ }
255
+ export function normalizeGitLabCredentialHost(hostOrApiBaseUrl) {
256
+ const value = hostOrApiBaseUrl?.trim();
257
+ if (!value || value === "gitlab.com" || value === "https://gitlab.com") {
258
+ return "gitlab.com";
259
+ }
260
+ const url = value.startsWith("http://") || value.startsWith("https://")
261
+ ? new URL(value)
262
+ : new URL(`https://${value}`);
263
+ return url.host;
264
+ }
265
+ export function defaultGitLabCredentialRunner(request, options) {
266
+ const result = spawnSync("git", ["credential", "fill"], {
267
+ input: gitLabCredentialInput(request),
268
+ encoding: "utf8",
269
+ env: {
270
+ ...process.env,
271
+ ...(options.interactive
272
+ ? {}
273
+ : {
274
+ GCM_INTERACTIVE: "0",
275
+ GIT_TERMINAL_PROMPT: "0",
276
+ }),
277
+ },
278
+ });
279
+ return {
280
+ status: result.status,
281
+ stdout: result.stdout ?? "",
282
+ stderr: result.stderr ?? "",
283
+ ...(result.error ? { error: result.error } : {}),
284
+ };
285
+ }
286
+ function fillGitLabCredential(runner, request, options) {
287
+ const result = runner(request, options);
288
+ if (result.status !== 0 || result.error) {
289
+ return undefined;
290
+ }
291
+ const credential = parseGitLabCredentialOutput(result.stdout);
292
+ return credential.password || (credential.authtype && credential.credential)
293
+ ? credential
294
+ : undefined;
295
+ }
296
+ function authorizationHeadersFromCredential(credential) {
297
+ const authtype = optionalNonEmptyString(credential.authtype, "authtype");
298
+ const encodedCredential = optionalNonEmptyString(credential.credential, "credential");
299
+ if (authtype && encodedCredential) {
300
+ return { Authorization: `${authtype} ${encodedCredential}` };
301
+ }
302
+ const password = optionalNonEmptyString(credential.password, "password");
303
+ return password ? { "PRIVATE-TOKEN": password } : {};
304
+ }
305
+ function gitLabCredentialInput(request) {
306
+ return [
307
+ `protocol=${request.protocol}`,
308
+ `host=${request.host}`,
309
+ ...(request.path ? [`path=${request.path}`] : []),
310
+ "",
311
+ ].join("\n");
312
+ }
313
+ function parseGitLabCredentialOutput(output) {
314
+ const credential = {};
315
+ for (const line of output.split(/\r?\n/)) {
316
+ if (line.length === 0) {
317
+ continue;
318
+ }
319
+ const separator = line.indexOf("=");
320
+ if (separator <= 0) {
321
+ continue;
322
+ }
323
+ credential[line.slice(0, separator)] = line.slice(separator + 1);
324
+ }
325
+ return credential;
326
+ }
327
+ function workStatusFromIssue(issue) {
328
+ const statusLabel = labelNames(issue).find((label) => label.startsWith(gitLabStatusLabelPrefix));
329
+ if (statusLabel) {
330
+ const candidate = statusLabel.slice(gitLabStatusLabelPrefix.length);
331
+ if (workStatuses.has(candidate)) {
332
+ return candidate;
333
+ }
334
+ }
335
+ return issue.state === "closed" ? "done" : "todo";
336
+ }
337
+ function gitLabStateForQuery(statuses) {
338
+ if (!statuses || statuses.size === 0) {
339
+ return "all";
340
+ }
341
+ let hasOpen = false;
342
+ let hasClosed = false;
343
+ for (const status of statuses) {
344
+ hasOpen ||= openStatuses.has(status);
345
+ hasClosed ||= closedStatuses.has(status);
346
+ }
347
+ return hasOpen && hasClosed ? "all" : hasClosed ? "closed" : "opened";
348
+ }
349
+ function labelsWithStatus(labels, status) {
350
+ const normalized = normalizeStringArray(labels, "labels").filter((label) => !label.startsWith(gitLabStatusLabelPrefix));
351
+ if (status &&
352
+ ((openStatuses.has(status) && status !== "todo") || status === "wont_do")) {
353
+ normalized.push(`${gitLabStatusLabelPrefix}${status}`);
354
+ }
355
+ return dedupeStrings(normalized);
356
+ }
357
+ function userLabelNames(issue) {
358
+ return labelNames(issue).filter((label) => !label.startsWith(gitLabStatusLabelPrefix));
359
+ }
360
+ function labelNames(issue) {
361
+ return (issue.labels ?? [])
362
+ .filter((label) => Boolean(label && label.trim()))
363
+ .map((label) => label.trim());
364
+ }
365
+ function assigneeUsernames(issue) {
366
+ return (issue.assignees ?? [])
367
+ .map((assignee) => assignee.username ?? String(assignee.id ?? ""))
368
+ .filter((login) => Boolean(login && login.trim()))
369
+ .map((login) => login.trim());
370
+ }
371
+ function issueIidFromRef(ref) {
372
+ if (ref.provider && ref.provider !== "gitlab") {
373
+ throw new GitLabWorkTrackerProviderError(`gitlab provider cannot resolve ${ref.provider} work item refs`);
374
+ }
375
+ if (ref.externalRef?.provider && ref.externalRef.provider !== "gitlab") {
376
+ throw new GitLabWorkTrackerProviderError(`gitlab provider cannot resolve ${ref.externalRef.provider} external refs`);
377
+ }
378
+ const candidate = ref.externalRef?.itemNumber ??
379
+ ref.id ??
380
+ ref.externalRef?.itemId;
381
+ if (candidate === undefined || candidate === null) {
382
+ throw new GitLabWorkTrackerProviderError("GitLab issue IID is required");
383
+ }
384
+ if (typeof candidate === "number") {
385
+ return positiveInteger(candidate, "issue IID");
386
+ }
387
+ const normalized = candidate.trim().replace(/^gitlab-/, "");
388
+ return positiveInteger(Number(normalized), "issue IID");
389
+ }
390
+ function normalizeStatusFilter(status) {
391
+ if (status === undefined) {
392
+ return undefined;
393
+ }
394
+ const values = Array.isArray(status) ? status : [status];
395
+ const normalized = new Set();
396
+ for (const value of values) {
397
+ assertWorkStatus(value);
398
+ normalized.add(value);
399
+ }
400
+ return normalized;
401
+ }
402
+ function matchesStatusFilter(item, statuses) {
403
+ return !statuses || statuses.size === 0 || statuses.has(item.status);
404
+ }
405
+ function matchesStringFilter(itemValues, requiredValues) {
406
+ return requiredValues.every((value) => itemValues?.includes(value));
407
+ }
408
+ function matchesSearch(item, search) {
409
+ return [item.id, item.title, item.description ?? ""].some((value) => value.toLowerCase().includes(search));
410
+ }
411
+ function requestLabels(labels) {
412
+ return labels.length > 0 ? { labels: labels.join(",") } : {};
413
+ }
414
+ function requestAssignees(assignees) {
415
+ const assigneeIds = normalizePositiveIntegerArray(assignees, "assignees");
416
+ return assigneeIds.length > 0 ? { assignee_ids: assigneeIds } : {};
417
+ }
418
+ function requestMilestone(value) {
419
+ if (value === undefined) {
420
+ return {};
421
+ }
422
+ if (value === null) {
423
+ return { milestone_id: null };
424
+ }
425
+ return { milestone_id: positiveInteger(Number(value), "milestone") };
426
+ }
427
+ function normalizeLimit(limit) {
428
+ if (limit === undefined) {
429
+ return undefined;
430
+ }
431
+ if (!Number.isInteger(limit) || limit < 0) {
432
+ throw new GitLabWorkTrackerProviderError("limit must be a non-negative integer");
433
+ }
434
+ return limit;
435
+ }
436
+ function normalizeStringArray(values, pathName) {
437
+ if (values === undefined) {
438
+ return [];
439
+ }
440
+ if (!Array.isArray(values)) {
441
+ throw new GitLabWorkTrackerProviderError(`${pathName} must be an array`);
442
+ }
443
+ return dedupeStrings(values.map((value, index) => requiredNonEmptyString(value, `${pathName}[${index}]`)));
444
+ }
445
+ function normalizePositiveIntegerArray(values, pathName) {
446
+ return normalizeStringArray(values, pathName).map((value, index) => positiveInteger(Number(value), `${pathName}[${index}]`));
447
+ }
448
+ function dedupeStrings(values) {
449
+ const seen = new Set();
450
+ const result = [];
451
+ for (const value of values) {
452
+ if (!seen.has(value)) {
453
+ seen.add(value);
454
+ result.push(value);
455
+ }
456
+ }
457
+ return result;
458
+ }
459
+ function assertWorkStatus(status) {
460
+ if (!workStatuses.has(status)) {
461
+ throw new GitLabWorkTrackerProviderError(`Invalid work status: ${status}`);
462
+ }
463
+ }
464
+ function positiveInteger(value, pathName) {
465
+ if (!Number.isInteger(value) || value < 1) {
466
+ throw new GitLabWorkTrackerProviderError(`${pathName} must be a positive integer`);
467
+ }
468
+ return value;
469
+ }
470
+ function requiredNonEmptyString(value, pathName) {
471
+ if (typeof value !== "string" || value.trim().length === 0) {
472
+ throw new GitLabWorkTrackerProviderError(`${pathName} must be a non-empty string`);
473
+ }
474
+ return value.trim();
475
+ }
476
+ function optionalNonEmptyString(value, pathName) {
477
+ if (value === undefined || value === null) {
478
+ return undefined;
479
+ }
480
+ if (value.trim().length === 0) {
481
+ return undefined;
482
+ }
483
+ return requiredNonEmptyString(value, pathName);
484
+ }
485
+ function encodePathSegment(value) {
486
+ return encodeURIComponent(requiredNonEmptyString(value, "path segment"));
487
+ }
488
+ async function gitLabErrorMessage(response, method, url) {
489
+ let detail;
490
+ try {
491
+ const body = (await response.json());
492
+ detail = gitLabErrorDetail(body);
493
+ }
494
+ catch {
495
+ detail = await response.text().catch(() => undefined);
496
+ }
497
+ return [
498
+ `GitLab request failed: ${method} ${url.pathname} returned ${response.status}`,
499
+ detail ? `: ${detail}` : "",
500
+ ].join("");
501
+ }
502
+ function gitLabErrorDetail(body) {
503
+ if (typeof body.error === "string") {
504
+ return body.error;
505
+ }
506
+ if (typeof body.message === "string") {
507
+ return body.message;
508
+ }
509
+ if (body.message && typeof body.message === "object") {
510
+ return JSON.stringify(body.message);
511
+ }
512
+ return undefined;
513
+ }
514
+ function gitLabErrorMessageWithCredentialHint(message, response, authorizationHeaders, config) {
515
+ if (authorizationHeaders.Authorization ||
516
+ authorizationHeaders["PRIVATE-TOKEN"] ||
517
+ (response.status !== 401 && response.status !== 403)) {
518
+ return message;
519
+ }
520
+ return (`${message}. No GitLab token or git credential was available for ` +
521
+ `${normalizeGitLabCredentialHost(config.host)}. Configure GITLAB_TOKEN, ` +
522
+ "GL_TOKEN, or a git credential helper.");
523
+ }
@@ -0,0 +1,67 @@
1
+ import type { CreateWorkItemInput, JiraWorkTrackingConfig, TrackerCapabilities, WorkComment, WorkItem, WorkItemPatch, WorkItemQuery, WorkItemRef, WorkStatus, WorkTrackerProvider } from "./workTrackingTypes.js";
2
+ export declare const jiraStatusLabelPrefix = "status:";
3
+ export declare const jiraRestApiPath = "/rest/api/3";
4
+ export interface JiraWorkTrackerProviderOptions {
5
+ config: JiraWorkTrackingConfig;
6
+ email?: string | null;
7
+ apiToken?: string | null;
8
+ token?: string | null;
9
+ fetch?: typeof fetch;
10
+ apiBaseUrl?: string | null;
11
+ env?: Record<string, string | undefined>;
12
+ credentialRunner?: JiraCredentialRunner | false;
13
+ credentialInteractive?: boolean;
14
+ }
15
+ export interface JiraCredentialCommandResult {
16
+ status: number | null;
17
+ stdout: string;
18
+ stderr: string;
19
+ error?: Error;
20
+ }
21
+ export interface JiraCredentialRequest {
22
+ protocol: "https";
23
+ host: string;
24
+ }
25
+ export type JiraCredentialRunner = (request: JiraCredentialRequest, options: {
26
+ interactive: boolean;
27
+ }) => JiraCredentialCommandResult;
28
+ export declare class JiraWorkTrackerProviderError extends Error {
29
+ constructor(message: string);
30
+ }
31
+ export declare const jiraWorkTrackerCapabilities: TrackerCapabilities;
32
+ export declare function jiraWorkTrackerCapabilitiesForConfig(config: Pick<JiraWorkTrackingConfig, "board">): TrackerCapabilities;
33
+ export declare function createJiraWorkTrackerProvider(options: JiraWorkTrackerProviderOptions): JiraWorkTrackerProvider;
34
+ export declare class JiraWorkTrackerProvider implements WorkTrackerProvider {
35
+ readonly provider = "jira";
36
+ readonly capabilities: TrackerCapabilities;
37
+ private readonly config;
38
+ private readonly fetchFn;
39
+ private readonly apiBaseUrl;
40
+ private readonly webBaseUrl;
41
+ private readonly staticAuthorizationHeader?;
42
+ private readonly credentialRunner?;
43
+ private readonly credentialInteractive;
44
+ private credentialAuthorizationHeader;
45
+ constructor(options: JiraWorkTrackerProviderOptions);
46
+ createWorkItem(input: CreateWorkItemInput): Promise<WorkItem>;
47
+ listWorkItems(query?: WorkItemQuery): Promise<WorkItem[]>;
48
+ getWorkItem(ref: WorkItemRef): Promise<WorkItem>;
49
+ updateWorkItem(ref: WorkItemRef, patch: WorkItemPatch): Promise<WorkItem>;
50
+ addComment(ref: WorkItemRef, body: string): Promise<WorkComment>;
51
+ setStatus(ref: WorkItemRef, status: WorkStatus): Promise<WorkItem>;
52
+ private getIssue;
53
+ private applyConfiguredTransition;
54
+ private requestJson;
55
+ private issueToWorkItem;
56
+ private commentToWorkComment;
57
+ private issueExternalRef;
58
+ private issueWebUrl;
59
+ private authorizationHeader;
60
+ }
61
+ export declare function normalizeJiraApiBaseUrl(hostOrApiBaseUrl?: string | null): string;
62
+ export declare function normalizeJiraWebBaseUrl(hostOrApiBaseUrl?: string | null): string;
63
+ export declare function jiraCredentialRequest(config: Pick<JiraWorkTrackingConfig, "host">): JiraCredentialRequest;
64
+ export declare function normalizeJiraCredentialHost(hostOrApiBaseUrl?: string | null): string;
65
+ export declare function defaultJiraCredentialRunner(request: JiraCredentialRequest, options: {
66
+ interactive: boolean;
67
+ }): JiraCredentialCommandResult;