@happyvertical/projects 0.78.2 → 0.78.3
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/README.md +16 -2
- package/dist/index.d.ts +57 -0
- package/dist/index.js +344 -13
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -130,6 +130,9 @@ Get project details including fields and statuses.
|
|
|
130
130
|
|
|
131
131
|
Add an issue or pull request to the project.
|
|
132
132
|
|
|
133
|
+
The GitHub adapter returns the created item's identity and content type. Use
|
|
134
|
+
`listItems()` when content metadata, status, or provider fields are required.
|
|
135
|
+
|
|
133
136
|
**Parameters:**
|
|
134
137
|
- `contentId: string` - Node ID of the issue or PR (from @happyvertical/repos)
|
|
135
138
|
|
|
@@ -146,6 +149,9 @@ Remove an item from the project.
|
|
|
146
149
|
|
|
147
150
|
Get a specific project item.
|
|
148
151
|
|
|
152
|
+
The GitHub adapter returns item identity and content type only. Use
|
|
153
|
+
`listItems()` for the enriched content metadata, status, and provider fields.
|
|
154
|
+
|
|
149
155
|
**Parameters:**
|
|
150
156
|
- `itemId: string` - Project item ID
|
|
151
157
|
|
|
@@ -153,10 +159,18 @@ Get a specific project item.
|
|
|
153
159
|
|
|
154
160
|
##### `listItems(filters?: ItemFilters): Promise<ProjectItem[]>`
|
|
155
161
|
|
|
156
|
-
List
|
|
162
|
+
List items in the project. The GitHub adapter follows Projects V2 pagination,
|
|
163
|
+
skips deleted or inaccessible content, and returns each item's content type,
|
|
164
|
+
status, provider field values, and top-level `title`, `url`, and `assignees`
|
|
165
|
+
metadata for provider-neutral rendering. Provider fields remain separately
|
|
166
|
+
keyed by their original names, even when a field has the same name as top-level
|
|
167
|
+
metadata. Results default to at most 100 items and are fetched in pages sized to
|
|
168
|
+
stay below GitHub's GraphQL node limit. A `limit` of 0 returns no items.
|
|
169
|
+
The GitHub adapter currently applies only `limit` and `cursor`; callers should
|
|
170
|
+
apply `status`, `assignees`, or `labels` filters to the returned items.
|
|
157
171
|
|
|
158
172
|
**Parameters:**
|
|
159
|
-
- `filters?: ItemFilters` - Optional filters (limit
|
|
173
|
+
- `filters?: ItemFilters` - Optional filters (`limit` defaults to 100)
|
|
160
174
|
|
|
161
175
|
**Returns:** Array of project items
|
|
162
176
|
|
package/dist/index.d.ts
CHANGED
|
@@ -62,10 +62,45 @@ export declare class GitHubProject implements IProject {
|
|
|
62
62
|
private statusOptions?;
|
|
63
63
|
constructor(config: ProjectConfig);
|
|
64
64
|
getProject(): Promise<Project>;
|
|
65
|
+
/**
|
|
66
|
+
* Add an issue or pull request to the configured GitHub project.
|
|
67
|
+
*
|
|
68
|
+
* The mutation result contains item identity and content type only. Use
|
|
69
|
+
* {@link listItems} when content metadata, status, or fields are required.
|
|
70
|
+
*
|
|
71
|
+
* @param contentId - GitHub node ID for the issue or pull request
|
|
72
|
+
* @returns The created provider-neutral project item
|
|
73
|
+
*/
|
|
65
74
|
addItem(contentId: string): Promise<ProjectItem>;
|
|
66
75
|
removeItem(itemId: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Get one GitHub Projects V2 item by its project-item ID.
|
|
78
|
+
*
|
|
79
|
+
* This identity lookup does not hydrate content metadata, status, or fields.
|
|
80
|
+
* Use {@link listItems} for the enriched board-item representation.
|
|
81
|
+
*
|
|
82
|
+
* @param itemId - GitHub project-item node ID
|
|
83
|
+
* @returns The item, or `null` when it or its content is inaccessible
|
|
84
|
+
*/
|
|
67
85
|
getItem(itemId: string): Promise<ProjectItem | null>;
|
|
86
|
+
/**
|
|
87
|
+
* List GitHub Projects V2 items after an optional cursor.
|
|
88
|
+
*
|
|
89
|
+
* Deleted or inaccessible content is skipped, and the result defaults to at
|
|
90
|
+
* most 100 accessible items to preserve the adapter's historical limit.
|
|
91
|
+
*
|
|
92
|
+
* @param filters - Optional cursor and maximum result count
|
|
93
|
+
* @returns Provider-neutral project items with content metadata and fields
|
|
94
|
+
*/
|
|
68
95
|
listItems(filters?: ItemFilters): Promise<ProjectItem[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Convert one GitHub Projects V2 item into the provider-neutral item shape.
|
|
98
|
+
*/
|
|
99
|
+
private mapProjectItem;
|
|
100
|
+
/**
|
|
101
|
+
* Convert GitHub's field-value union into stable scalar or string-array values.
|
|
102
|
+
*/
|
|
103
|
+
private mapProjectFieldValue;
|
|
69
104
|
updateItemStatus(itemId: string, status: string): Promise<void>;
|
|
70
105
|
listStatuses(): Promise<Status[]>;
|
|
71
106
|
updateItemField(itemId: string, fieldId: string, value: unknown): Promise<void>;
|
|
@@ -91,11 +126,19 @@ export declare interface IProject {
|
|
|
91
126
|
listFields(): Promise<Field[]>;
|
|
92
127
|
}
|
|
93
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Options for reading project items.
|
|
131
|
+
*/
|
|
94
132
|
export declare interface ItemFilters {
|
|
133
|
+
/** Return items in this status when supported by the provider. */
|
|
95
134
|
status?: string;
|
|
135
|
+
/** Return items assigned to these provider usernames when supported. */
|
|
96
136
|
assignees?: string[];
|
|
137
|
+
/** Return items with these labels when supported. */
|
|
97
138
|
labels?: string[];
|
|
139
|
+
/** Maximum number of items to return. Defaults to 100; zero returns none. */
|
|
98
140
|
limit?: number;
|
|
141
|
+
/** Provider cursor after which item pagination begins. */
|
|
99
142
|
cursor?: string;
|
|
100
143
|
}
|
|
101
144
|
|
|
@@ -159,11 +202,25 @@ export declare enum ProjectErrorCode {
|
|
|
159
202
|
UNKNOWN = "UNKNOWN"
|
|
160
203
|
}
|
|
161
204
|
|
|
205
|
+
/**
|
|
206
|
+
* A provider-neutral item on a project board.
|
|
207
|
+
*/
|
|
162
208
|
export declare interface ProjectItem {
|
|
209
|
+
/** Provider project-item ID. */
|
|
163
210
|
id: string;
|
|
211
|
+
/** Provider content-node ID for the issue, pull request, or draft issue. */
|
|
164
212
|
contentId: string;
|
|
213
|
+
/** Content title when returned by the provider. */
|
|
214
|
+
title?: string;
|
|
215
|
+
/** Content URL, or `null` when the content has no provider URL. */
|
|
216
|
+
url?: string | null;
|
|
217
|
+
/** Provider usernames assigned to the content. */
|
|
218
|
+
assignees?: string[];
|
|
219
|
+
/** Selected status name from the configured or canonical status field. */
|
|
165
220
|
status?: string;
|
|
221
|
+
/** Provider field values keyed by field name. */
|
|
166
222
|
fields: Record<string, unknown>;
|
|
223
|
+
/** Kind of content represented by the project item. */
|
|
167
224
|
type: 'Issue' | 'PullRequest' | 'DraftIssue';
|
|
168
225
|
}
|
|
169
226
|
|
package/dist/index.js
CHANGED
|
@@ -107,6 +107,8 @@ async function getProject(options) {
|
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
+
const GITHUB_DEFAULT_ITEMS_LIMIT = 100;
|
|
111
|
+
const GITHUB_ITEMS_PAGE_SIZE = 20;
|
|
110
112
|
class GitHubProject {
|
|
111
113
|
graphql;
|
|
112
114
|
projectId;
|
|
@@ -203,6 +205,15 @@ class GitHubProject {
|
|
|
203
205
|
};
|
|
204
206
|
}
|
|
205
207
|
// Items
|
|
208
|
+
/**
|
|
209
|
+
* Add an issue or pull request to the configured GitHub project.
|
|
210
|
+
*
|
|
211
|
+
* The mutation result contains item identity and content type only. Use
|
|
212
|
+
* {@link listItems} when content metadata, status, or fields are required.
|
|
213
|
+
*
|
|
214
|
+
* @param contentId - GitHub node ID for the issue or pull request
|
|
215
|
+
* @returns The created provider-neutral project item
|
|
216
|
+
*/
|
|
206
217
|
async addItem(contentId) {
|
|
207
218
|
const mutation = `
|
|
208
219
|
mutation($projectId: ID!, $contentId: ID!) {
|
|
@@ -215,9 +226,11 @@ class GitHubProject {
|
|
|
215
226
|
content {
|
|
216
227
|
... on Issue {
|
|
217
228
|
id
|
|
229
|
+
typename: __typename
|
|
218
230
|
}
|
|
219
231
|
... on PullRequest {
|
|
220
232
|
id
|
|
233
|
+
typename: __typename
|
|
221
234
|
}
|
|
222
235
|
}
|
|
223
236
|
}
|
|
@@ -232,7 +245,7 @@ class GitHubProject {
|
|
|
232
245
|
id: data.addProjectV2ItemById.item.id,
|
|
233
246
|
contentId: data.addProjectV2ItemById.item.content.id,
|
|
234
247
|
fields: {},
|
|
235
|
-
type:
|
|
248
|
+
type: data.addProjectV2ItemById.item.content.typename
|
|
236
249
|
};
|
|
237
250
|
}
|
|
238
251
|
async removeItem(itemId) {
|
|
@@ -251,6 +264,15 @@ class GitHubProject {
|
|
|
251
264
|
itemId
|
|
252
265
|
});
|
|
253
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Get one GitHub Projects V2 item by its project-item ID.
|
|
269
|
+
*
|
|
270
|
+
* This identity lookup does not hydrate content metadata, status, or fields.
|
|
271
|
+
* Use {@link listItems} for the enriched board-item representation.
|
|
272
|
+
*
|
|
273
|
+
* @param itemId - GitHub project-item node ID
|
|
274
|
+
* @returns The item, or `null` when it or its content is inaccessible
|
|
275
|
+
*/
|
|
254
276
|
async getItem(itemId) {
|
|
255
277
|
const query = `
|
|
256
278
|
query($itemId: ID!) {
|
|
@@ -260,9 +282,15 @@ class GitHubProject {
|
|
|
260
282
|
content {
|
|
261
283
|
... on Issue {
|
|
262
284
|
id
|
|
285
|
+
typename: __typename
|
|
263
286
|
}
|
|
264
287
|
... on PullRequest {
|
|
265
288
|
id
|
|
289
|
+
typename: __typename
|
|
290
|
+
}
|
|
291
|
+
... on DraftIssue {
|
|
292
|
+
id
|
|
293
|
+
typename: __typename
|
|
266
294
|
}
|
|
267
295
|
}
|
|
268
296
|
}
|
|
@@ -270,16 +298,25 @@ class GitHubProject {
|
|
|
270
298
|
}
|
|
271
299
|
`;
|
|
272
300
|
const data = await this.graphql.query(query, { itemId });
|
|
273
|
-
if (!data.node) {
|
|
301
|
+
if (!data.node?.content?.id || !data.node.content.typename) {
|
|
274
302
|
return null;
|
|
275
303
|
}
|
|
276
304
|
return {
|
|
277
305
|
id: data.node.id,
|
|
278
306
|
contentId: data.node.content.id,
|
|
279
307
|
fields: {},
|
|
280
|
-
type:
|
|
308
|
+
type: data.node.content.typename
|
|
281
309
|
};
|
|
282
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* List GitHub Projects V2 items after an optional cursor.
|
|
313
|
+
*
|
|
314
|
+
* Deleted or inaccessible content is skipped, and the result defaults to at
|
|
315
|
+
* most 100 accessible items to preserve the adapter's historical limit.
|
|
316
|
+
*
|
|
317
|
+
* @param filters - Optional cursor and maximum result count
|
|
318
|
+
* @returns Provider-neutral project items with content metadata and fields
|
|
319
|
+
*/
|
|
283
320
|
async listItems(filters) {
|
|
284
321
|
const query = `
|
|
285
322
|
query($projectId: ID!, $first: Int!, $after: String) {
|
|
@@ -291,9 +328,169 @@ class GitHubProject {
|
|
|
291
328
|
content {
|
|
292
329
|
... on Issue {
|
|
293
330
|
id
|
|
331
|
+
typename: __typename
|
|
332
|
+
title
|
|
333
|
+
url
|
|
334
|
+
assignees(first: 100) {
|
|
335
|
+
nodes {
|
|
336
|
+
login
|
|
337
|
+
}
|
|
338
|
+
}
|
|
294
339
|
}
|
|
295
340
|
... on PullRequest {
|
|
296
341
|
id
|
|
342
|
+
typename: __typename
|
|
343
|
+
title
|
|
344
|
+
url
|
|
345
|
+
assignees(first: 100) {
|
|
346
|
+
nodes {
|
|
347
|
+
login
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
... on DraftIssue {
|
|
352
|
+
id
|
|
353
|
+
typename: __typename
|
|
354
|
+
title
|
|
355
|
+
assignees(first: 100) {
|
|
356
|
+
nodes {
|
|
357
|
+
login
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
fieldValues(first: 50) {
|
|
363
|
+
nodes {
|
|
364
|
+
typename: __typename
|
|
365
|
+
# GitHub Projects have at most 50 fields. A 20-item page and
|
|
366
|
+
# four 100-node nested connections stay below GitHub's
|
|
367
|
+
# 500,000-node query limit while avoiding partial values.
|
|
368
|
+
... on ProjectV2ItemFieldDateValue {
|
|
369
|
+
field {
|
|
370
|
+
...ProjectField
|
|
371
|
+
}
|
|
372
|
+
date
|
|
373
|
+
}
|
|
374
|
+
... on ProjectV2ItemFieldIterationValue {
|
|
375
|
+
field {
|
|
376
|
+
...ProjectField
|
|
377
|
+
}
|
|
378
|
+
title
|
|
379
|
+
}
|
|
380
|
+
... on ProjectV2ItemFieldLabelValue {
|
|
381
|
+
field {
|
|
382
|
+
...ProjectField
|
|
383
|
+
}
|
|
384
|
+
labels(first: 100) {
|
|
385
|
+
nodes {
|
|
386
|
+
name
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
... on ProjectV2ItemFieldMilestoneValue {
|
|
391
|
+
field {
|
|
392
|
+
...ProjectField
|
|
393
|
+
}
|
|
394
|
+
milestone {
|
|
395
|
+
title
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
... on ProjectV2ItemFieldNumberValue {
|
|
399
|
+
field {
|
|
400
|
+
...ProjectField
|
|
401
|
+
}
|
|
402
|
+
number
|
|
403
|
+
}
|
|
404
|
+
... on ProjectV2ItemFieldPullRequestValue {
|
|
405
|
+
field {
|
|
406
|
+
...ProjectField
|
|
407
|
+
}
|
|
408
|
+
pullRequests(first: 100) {
|
|
409
|
+
nodes {
|
|
410
|
+
url
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
... on ProjectV2ItemFieldRepositoryValue {
|
|
415
|
+
field {
|
|
416
|
+
...ProjectField
|
|
417
|
+
}
|
|
418
|
+
repository {
|
|
419
|
+
nameWithOwner
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
... on ProjectV2ItemFieldReviewerValue {
|
|
423
|
+
field {
|
|
424
|
+
...ProjectField
|
|
425
|
+
}
|
|
426
|
+
reviewers(first: 100) {
|
|
427
|
+
nodes {
|
|
428
|
+
... on Bot {
|
|
429
|
+
login
|
|
430
|
+
}
|
|
431
|
+
... on EnterpriseTeam {
|
|
432
|
+
slug
|
|
433
|
+
}
|
|
434
|
+
... on Mannequin {
|
|
435
|
+
login
|
|
436
|
+
}
|
|
437
|
+
... on Team {
|
|
438
|
+
slug
|
|
439
|
+
}
|
|
440
|
+
... on User {
|
|
441
|
+
login
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
... on ProjectV2ItemFieldSingleSelectValue {
|
|
447
|
+
field {
|
|
448
|
+
...ProjectField
|
|
449
|
+
}
|
|
450
|
+
name
|
|
451
|
+
}
|
|
452
|
+
... on ProjectV2ItemFieldTextValue {
|
|
453
|
+
field {
|
|
454
|
+
...ProjectField
|
|
455
|
+
}
|
|
456
|
+
text
|
|
457
|
+
}
|
|
458
|
+
... on ProjectV2ItemFieldUserValue {
|
|
459
|
+
field {
|
|
460
|
+
...ProjectField
|
|
461
|
+
}
|
|
462
|
+
users(first: 100) {
|
|
463
|
+
nodes {
|
|
464
|
+
login
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
... on ProjectV2ItemIssueFieldValue {
|
|
469
|
+
field {
|
|
470
|
+
...ProjectField
|
|
471
|
+
}
|
|
472
|
+
issueFieldValue {
|
|
473
|
+
typename: __typename
|
|
474
|
+
... on IssueFieldDateValue {
|
|
475
|
+
dateValue: value
|
|
476
|
+
}
|
|
477
|
+
... on IssueFieldMultiSelectValue {
|
|
478
|
+
multiSelectOptions: options {
|
|
479
|
+
name
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
... on IssueFieldNumberValue {
|
|
483
|
+
numberValue: value
|
|
484
|
+
}
|
|
485
|
+
... on IssueFieldSingleSelectValue {
|
|
486
|
+
name
|
|
487
|
+
singleSelectValue: value
|
|
488
|
+
}
|
|
489
|
+
... on IssueFieldTextValue {
|
|
490
|
+
textValue: value
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
297
494
|
}
|
|
298
495
|
}
|
|
299
496
|
}
|
|
@@ -305,18 +502,152 @@ class GitHubProject {
|
|
|
305
502
|
}
|
|
306
503
|
}
|
|
307
504
|
}
|
|
505
|
+
|
|
506
|
+
fragment ProjectField on ProjectV2FieldCommon {
|
|
507
|
+
id
|
|
508
|
+
name
|
|
509
|
+
}
|
|
308
510
|
`;
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
511
|
+
const requestedLimit = filters?.limit ?? GITHUB_DEFAULT_ITEMS_LIMIT;
|
|
512
|
+
const limit = Number.isFinite(requestedLimit) ? Math.max(0, Math.floor(requestedLimit)) : GITHUB_DEFAULT_ITEMS_LIMIT;
|
|
513
|
+
if (limit === 0) {
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
const items = [];
|
|
517
|
+
let after = filters?.cursor;
|
|
518
|
+
let fillPage = false;
|
|
519
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
520
|
+
while (items.length < limit) {
|
|
521
|
+
const remaining = limit - items.length;
|
|
522
|
+
const first = fillPage ? GITHUB_ITEMS_PAGE_SIZE : Math.min(GITHUB_ITEMS_PAGE_SIZE, remaining);
|
|
523
|
+
const data = await this.graphql.query(query, {
|
|
524
|
+
projectId: this.projectId,
|
|
525
|
+
first,
|
|
526
|
+
after
|
|
527
|
+
});
|
|
528
|
+
if (!data.node) {
|
|
529
|
+
throw new ProjectError(
|
|
530
|
+
`GitHub project ${this.projectId} was not found or is inaccessible`,
|
|
531
|
+
ProjectErrorCode.NOT_FOUND
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const connection = data.node.items;
|
|
535
|
+
let skippedContent = false;
|
|
536
|
+
for (const node of connection.nodes) {
|
|
537
|
+
if (!node) {
|
|
538
|
+
skippedContent = true;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
const item = this.mapProjectItem(node);
|
|
542
|
+
if (item) {
|
|
543
|
+
items.push(item);
|
|
544
|
+
} else {
|
|
545
|
+
skippedContent = true;
|
|
546
|
+
}
|
|
547
|
+
if (items.length >= limit) {
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
fillPage = skippedContent;
|
|
552
|
+
const { endCursor, hasNextPage } = connection.pageInfo;
|
|
553
|
+
if (!hasNextPage || !endCursor || endCursor === after || seenCursors.has(endCursor)) {
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
seenCursors.add(endCursor);
|
|
557
|
+
after = endCursor;
|
|
558
|
+
}
|
|
559
|
+
return items;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Convert one GitHub Projects V2 item into the provider-neutral item shape.
|
|
563
|
+
*/
|
|
564
|
+
mapProjectItem(item) {
|
|
565
|
+
const { content } = item;
|
|
566
|
+
if (!content) {
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
const fieldEntries = [];
|
|
570
|
+
let status;
|
|
571
|
+
for (const fieldValue of item.fieldValues.nodes) {
|
|
572
|
+
if (!fieldValue?.field) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const value = this.mapProjectFieldValue(fieldValue);
|
|
576
|
+
if (value !== void 0) {
|
|
577
|
+
fieldEntries.push([fieldValue.field.name, value]);
|
|
578
|
+
}
|
|
579
|
+
const isStatusField = this.statusFieldId ? fieldValue.field.id === this.statusFieldId : fieldValue.field.name.toLowerCase() === "status";
|
|
580
|
+
if (isStatusField && typeof value === "string") {
|
|
581
|
+
status = value;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return {
|
|
315
585
|
id: item.id,
|
|
316
|
-
contentId:
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
586
|
+
contentId: content.id,
|
|
587
|
+
title: content.title,
|
|
588
|
+
url: content.url ?? null,
|
|
589
|
+
assignees: content.assignees.nodes.flatMap(
|
|
590
|
+
(assignee) => assignee ? [assignee.login] : []
|
|
591
|
+
),
|
|
592
|
+
status,
|
|
593
|
+
fields: Object.fromEntries(fieldEntries),
|
|
594
|
+
type: content.typename
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Convert GitHub's field-value union into stable scalar or string-array values.
|
|
599
|
+
*/
|
|
600
|
+
mapProjectFieldValue(value) {
|
|
601
|
+
switch (value.typename) {
|
|
602
|
+
case "ProjectV2ItemFieldDateValue":
|
|
603
|
+
return value.date;
|
|
604
|
+
case "ProjectV2ItemFieldIterationValue":
|
|
605
|
+
return value.title;
|
|
606
|
+
case "ProjectV2ItemFieldLabelValue":
|
|
607
|
+
return value.labels?.nodes.flatMap(
|
|
608
|
+
(label) => label ? [label.name] : []
|
|
609
|
+
);
|
|
610
|
+
case "ProjectV2ItemFieldMilestoneValue":
|
|
611
|
+
return value.milestone?.title;
|
|
612
|
+
case "ProjectV2ItemFieldNumberValue":
|
|
613
|
+
return value.number;
|
|
614
|
+
case "ProjectV2ItemFieldPullRequestValue":
|
|
615
|
+
return value.pullRequests?.nodes.flatMap(
|
|
616
|
+
(pullRequest) => pullRequest ? [pullRequest.url] : []
|
|
617
|
+
);
|
|
618
|
+
case "ProjectV2ItemFieldRepositoryValue":
|
|
619
|
+
return value.repository?.nameWithOwner;
|
|
620
|
+
case "ProjectV2ItemFieldReviewerValue":
|
|
621
|
+
return value.reviewers?.nodes.flatMap((reviewer) => {
|
|
622
|
+
const name = reviewer?.login ?? reviewer?.slug;
|
|
623
|
+
return name ? [name] : [];
|
|
624
|
+
});
|
|
625
|
+
case "ProjectV2ItemFieldSingleSelectValue":
|
|
626
|
+
return value.name;
|
|
627
|
+
case "ProjectV2ItemFieldTextValue":
|
|
628
|
+
return value.text;
|
|
629
|
+
case "ProjectV2ItemFieldUserValue":
|
|
630
|
+
return value.users?.nodes.flatMap((user) => user ? [user.login] : []);
|
|
631
|
+
case "ProjectV2ItemIssueFieldValue": {
|
|
632
|
+
const issueValue = value.issueFieldValue;
|
|
633
|
+
switch (issueValue?.typename) {
|
|
634
|
+
case "IssueFieldDateValue":
|
|
635
|
+
return issueValue.dateValue;
|
|
636
|
+
case "IssueFieldMultiSelectValue":
|
|
637
|
+
return issueValue.multiSelectOptions?.map((option) => option.name);
|
|
638
|
+
case "IssueFieldNumberValue":
|
|
639
|
+
return issueValue.numberValue;
|
|
640
|
+
case "IssueFieldSingleSelectValue":
|
|
641
|
+
return issueValue.name ?? issueValue.singleSelectValue;
|
|
642
|
+
case "IssueFieldTextValue":
|
|
643
|
+
return issueValue.textValue;
|
|
644
|
+
default:
|
|
645
|
+
return void 0;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
default:
|
|
649
|
+
return void 0;
|
|
650
|
+
}
|
|
320
651
|
}
|
|
321
652
|
// Status Management
|
|
322
653
|
async updateItemStatus(itemId, status) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/errors.ts","../src/factory.ts","../src/github/index.ts","../src/types.ts"],"sourcesContent":["/**\n * Project error handling\n */\n\nexport enum ProjectErrorCode {\n NOT_FOUND = 'NOT_FOUND',\n UNAUTHORIZED = 'UNAUTHORIZED',\n INVALID_STATUS = 'INVALID_STATUS',\n INVALID_FIELD = 'INVALID_FIELD',\n ITEM_NOT_IN_PROJECT = 'ITEM_NOT_IN_PROJECT',\n RATE_LIMITED = 'RATE_LIMITED',\n NETWORK_ERROR = 'NETWORK_ERROR',\n VALIDATION_ERROR = 'VALIDATION_ERROR',\n UNKNOWN = 'UNKNOWN',\n}\n\nexport class ProjectError extends Error {\n constructor(\n message: string,\n public code: ProjectErrorCode,\n public statusCode?: number,\n public response?: unknown,\n ) {\n super(message);\n this.name = 'ProjectError';\n Error.captureStackTrace(this, this.constructor);\n }\n\n static fromHTTPStatus(\n statusCode: number,\n message: string,\n response?: unknown,\n ): ProjectError {\n let code: ProjectErrorCode;\n\n switch (statusCode) {\n case 404:\n code = ProjectErrorCode.NOT_FOUND;\n break;\n case 401:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 403:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 429:\n code = ProjectErrorCode.RATE_LIMITED;\n break;\n case 422:\n code = ProjectErrorCode.VALIDATION_ERROR;\n break;\n default:\n code = ProjectErrorCode.UNKNOWN;\n }\n\n return new ProjectError(message, code, statusCode, response);\n }\n\n static networkError(message: string, cause?: Error): ProjectError {\n return new ProjectError(\n message,\n ProjectErrorCode.NETWORK_ERROR,\n undefined,\n cause,\n );\n }\n\n isRetryable(): boolean {\n return (\n this.code === ProjectErrorCode.RATE_LIMITED ||\n this.code === ProjectErrorCode.NETWORK_ERROR\n );\n }\n}\n","/**\n * Project factory function\n *\n * Creates project instances following the pattern from @happyvertical/files and @happyvertical/sql\n */\n\nimport { ProjectError, ProjectErrorCode } from './errors.js';\nimport type { IProject, ProjectConfig } from './types.js';\n\n/**\n * Check if value is already a project instance\n */\nfunction isProjectInstance(value: unknown): value is IProject {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'getProject' in value &&\n 'addItem' in value &&\n 'updateItemStatus' in value &&\n typeof (value as IProject).getProject === 'function'\n );\n}\n\n/**\n * Get a project management client instance\n *\n * This is the main entry point following the pattern from @happyvertical/files and @happyvertical/sql.\n * It can accept either a configuration object or an existing project instance.\n *\n * @param options - Project configuration or existing project instance\n * @returns Promise resolving to project interface\n *\n * @example\n * ```typescript\n * import { getProject } from '@happyvertical/projects';\n *\n * // Create a GitHub Projects client\n * const project = await getProject({\n * type: 'github',\n * owner: 'happyvertical',\n * projectId: 'PVT_kwDOB9Y8ns4A8-TY',\n * token: process.env.GITHUB_TOKEN,\n * statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',\n * statusOptions: {\n * 'New': 'option-id-1',\n * 'Backlog': 'option-id-2',\n * 'In Progress': 'option-id-3',\n * 'Done': 'option-id-4'\n * }\n * });\n *\n * // Use the client\n * await project.addItem(issueNodeId);\n * await project.updateItemStatus(itemId, 'In Progress');\n *\n * // Pass existing instance (returns it unchanged)\n * const sameProject = await getProject(project);\n * ```\n */\nexport async function getProject(\n options: ProjectConfig | IProject,\n): Promise<IProject> {\n // If already a project instance, return it\n if (isProjectInstance(options)) {\n return options;\n }\n\n // Validate configuration\n if (!options.type) {\n throw new ProjectError(\n 'Project type is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.projectId) {\n throw new ProjectError(\n 'Project ID is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.token) {\n throw new ProjectError(\n 'Authentication token is required',\n ProjectErrorCode.UNAUTHORIZED,\n );\n }\n\n // Create project instance based on type\n switch (options.type) {\n case 'github': {\n const { GitHubProject } = await import('./github/index.js');\n return new GitHubProject(options);\n }\n\n case 'jira':\n throw new ProjectError(\n 'Jira support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'zenhub':\n throw new ProjectError(\n 'ZenHub support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'linear':\n throw new ProjectError(\n 'Linear support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n default:\n throw new ProjectError(\n `Unsupported project type: ${options.type}`,\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n}\n","/**\n * GitHub Projects V2 implementation\n */\n\nimport { GraphQLClient, type IGraphQLClient } from '@happyvertical/graphql';\nimport { ProjectError, ProjectErrorCode } from '../errors.js';\nimport type {\n Field,\n IProject,\n ItemFilters,\n Project,\n ProjectConfig,\n ProjectItem,\n Status,\n} from '../types.js';\n\n/**\n * GitHub Projects V2 implementation\n */\nexport class GitHubProject implements IProject {\n private graphql: IGraphQLClient;\n private projectId: string;\n private owner?: string;\n private repo?: string;\n private statusFieldId?: string;\n private statusOptions?: Record<string, string>;\n\n constructor(config: ProjectConfig) {\n if (config.type !== 'github') {\n throw new Error('Invalid config type for GitHubProject');\n }\n\n this.graphql = new GraphQLClient({\n endpoint: 'https://api.github.com/graphql',\n token: config.token,\n });\n this.projectId = config.projectId;\n this.owner = config.owner;\n this.repo = config.repo;\n this.statusFieldId = config.statusFieldId;\n this.statusOptions = config.statusOptions;\n }\n\n // Project Info\n async getProject(): Promise<Project> {\n const query = `\n query($projectId: ID!) {\n node(id: $projectId) {\n ... on ProjectV2 {\n id\n title\n shortDescription\n url\n owner {\n ... on Organization {\n login\n }\n ... on User {\n login\n }\n }\n fields(first: 20) {\n nodes {\n ... on ProjectV2Field {\n id\n name\n dataType\n }\n ... on ProjectV2SingleSelectField {\n id\n name\n dataType\n options {\n id\n name\n description\n color\n }\n }\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, {\n projectId: this.projectId,\n })) as {\n node: {\n id: string;\n title: string;\n shortDescription: string;\n url: string;\n owner: { login: string };\n fields: {\n nodes: Array<{\n id: string;\n name: string;\n dataType: string;\n options?: Array<{\n id: string;\n name: string;\n description?: string;\n color?: string;\n }>;\n }>;\n };\n };\n };\n\n const statusField = data.node.fields.nodes.find(\n (f) => f.name === 'Status' && f.options,\n );\n\n return {\n id: data.node.id,\n title: data.node.title,\n description: data.node.shortDescription,\n owner: data.node.owner.login,\n url: data.node.url,\n statuses: statusField?.options\n ? statusField.options.map((opt, index) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n order: index,\n }))\n : [],\n fields: data.node.fields.nodes.map((f) => ({\n id: f.id,\n name: f.name,\n type: this.mapFieldType(f.dataType),\n options: f.options?.map((opt) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n })),\n })),\n };\n }\n\n // Items\n async addItem(contentId: string): Promise<ProjectItem> {\n const mutation = `\n mutation($projectId: ID!, $contentId: ID!) {\n addProjectV2ItemById(input: {\n projectId: $projectId\n contentId: $contentId\n }) {\n item {\n id\n content {\n ... on Issue {\n id\n }\n ... on PullRequest {\n id\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n contentId,\n })) as {\n addProjectV2ItemById: {\n item: {\n id: string;\n content: {\n id: string;\n };\n };\n };\n };\n\n return {\n id: data.addProjectV2ItemById.item.id,\n contentId: data.addProjectV2ItemById.item.content.id,\n fields: {},\n type: 'Issue',\n };\n }\n\n async removeItem(itemId: string): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!) {\n deleteProjectV2Item(input: {\n projectId: $projectId\n itemId: $itemId\n }) {\n deletedItemId\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n });\n }\n\n async getItem(itemId: string): Promise<ProjectItem | null> {\n const query = `\n query($itemId: ID!) {\n node(id: $itemId) {\n ... on ProjectV2Item {\n id\n content {\n ... on Issue {\n id\n }\n ... on PullRequest {\n id\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, { itemId })) as {\n node: {\n id: string;\n content: {\n id: string;\n };\n } | null;\n };\n\n if (!data.node) {\n return null;\n }\n\n return {\n id: data.node.id,\n contentId: data.node.content.id,\n fields: {},\n type: 'Issue',\n };\n }\n\n async listItems(filters?: ItemFilters): Promise<ProjectItem[]> {\n const query = `\n query($projectId: ID!, $first: Int!, $after: String) {\n node(id: $projectId) {\n ... on ProjectV2 {\n items(first: $first, after: $after) {\n nodes {\n id\n content {\n ... on Issue {\n id\n }\n ... on PullRequest {\n id\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, {\n projectId: this.projectId,\n first: filters?.limit || 100,\n after: filters?.cursor,\n })) as {\n node: {\n items: {\n nodes: Array<{\n id: string;\n content: {\n id: string;\n };\n }>;\n };\n };\n };\n\n return data.node.items.nodes.map((item) => ({\n id: item.id,\n contentId: item.content.id,\n fields: {},\n type: 'Issue' as const,\n }));\n }\n\n // Status Management\n async updateItemStatus(itemId: string, status: string): Promise<void> {\n if (!this.statusFieldId || !this.statusOptions) {\n throw new ProjectError(\n 'Status field configuration is required. Provide statusFieldId and statusOptions in config.',\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n const statusOptionId = this.statusOptions[status];\n if (!statusOptionId) {\n throw new ProjectError(\n `Status \"${status}\" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(', ')}`,\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n await this.updateItemField(itemId, this.statusFieldId, statusOptionId);\n }\n\n async listStatuses(): Promise<Status[]> {\n const project = await this.getProject();\n return project.statuses;\n }\n\n // Field Management\n async updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {\n updateProjectV2ItemFieldValue(input: {\n projectId: $projectId\n itemId: $itemId\n fieldId: $fieldId\n value: $value\n }) {\n projectV2Item {\n id\n }\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n fieldId,\n value: { singleSelectOptionId: value },\n });\n }\n\n async listFields(): Promise<Field[]> {\n const project = await this.getProject();\n return project.fields;\n }\n\n /**\n * Map GitHub field type to our standard types\n */\n private mapFieldType(\n dataType: string,\n ): 'text' | 'number' | 'date' | 'single_select' | 'iteration' {\n switch (dataType.toLowerCase()) {\n case 'text':\n return 'text';\n case 'number':\n return 'number';\n case 'date':\n return 'date';\n case 'single_select':\n return 'single_select';\n case 'iteration':\n return 'iteration';\n default:\n return 'text';\n }\n }\n}\n","/**\n * Core types for project management operations\n */\n\nexport interface Project {\n id: string;\n title: string;\n description?: string;\n owner: string; // Organization or user\n url: string;\n statuses: Status[];\n fields: Field[];\n}\n\nexport interface ProjectItem {\n id: string; // Project item ID\n contentId: string; // Issue/PR node ID\n status?: string;\n fields: Record<string, unknown>;\n type: 'Issue' | 'PullRequest' | 'DraftIssue';\n}\n\nexport interface Status {\n id: string;\n name: string;\n description?: string;\n color?: string;\n order: number;\n}\n\nexport interface Field {\n id: string;\n name: string;\n type: 'text' | 'number' | 'date' | 'single_select' | 'iteration';\n options?: FieldOption[];\n}\n\nexport interface FieldOption {\n id: string;\n name: string;\n description?: string;\n color?: string;\n}\n\nexport interface ItemFilters {\n status?: string;\n assignees?: string[];\n labels?: string[];\n limit?: number;\n cursor?: string; // For pagination\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n type: 'github' | 'jira' | 'zenhub' | 'linear';\n projectId: string;\n token: string;\n\n // GitHub Projects V2\n owner?: string; // Organization or user\n repo?: string; // Repository name (for repo-level projects)\n statusFieldId?: string;\n statusOptions?: Record<string, string>; // Map status name to option ID\n\n // Jira\n boardId?: string;\n\n // Linear\n teamId?: string;\n}\n\n/**\n * Kanban status names for 6-lane structure\n */\nexport const KANBAN_STATUSES = [\n 'New',\n 'Backlog',\n 'Planning',\n 'Ready',\n 'In Progress',\n 'Review',\n 'Done',\n] as const;\n\nexport type KanbanStatus = (typeof KANBAN_STATUSES)[number];\n\n/**\n * Project interface - all project implementations must implement this\n */\nexport interface IProject {\n // Project Info\n getProject(): Promise<Project>;\n\n // Items (Issues/Cards)\n addItem(contentId: string): Promise<ProjectItem>;\n removeItem(itemId: string): Promise<void>;\n getItem(itemId: string): Promise<ProjectItem | null>;\n listItems(filters?: ItemFilters): Promise<ProjectItem[]>;\n\n // Status/Column Management\n updateItemStatus(itemId: string, status: string): Promise<void>;\n listStatuses(): Promise<Status[]>;\n\n // Field Management\n updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void>;\n listFields(): Promise<Field[]>;\n}\n"],"names":["ProjectErrorCode","GitHubProject","index"],"mappings":";AAIO,IAAK,qCAAAA,sBAAL;AACLA,oBAAA,WAAA,IAAY;AACZA,oBAAA,cAAA,IAAe;AACfA,oBAAA,gBAAA,IAAiB;AACjBA,oBAAA,eAAA,IAAgB;AAChBA,oBAAA,qBAAA,IAAsB;AACtBA,oBAAA,cAAA,IAAe;AACfA,oBAAA,eAAA,IAAgB;AAChBA,oBAAA,kBAAA,IAAmB;AACnBA,oBAAA,SAAA,IAAU;AATA,SAAAA;AAAA,GAAA,oBAAA,CAAA,CAAA;AAYL,MAAM,qBAAqB,MAAM;AAAA,EACtC,YACE,SACO,MACA,YACA,UACP;AACA,UAAM,OAAO;AAJN,SAAA,OAAA;AACA,SAAA,aAAA;AACA,SAAA,WAAA;AAGP,SAAK,OAAO;AACZ,UAAM,kBAAkB,MAAM,KAAK,WAAW;AAAA,EAChD;AAAA,EAEA,OAAO,eACL,YACA,SACA,UACc;AACd,QAAI;AAEJ,YAAQ,YAAA;AAAA,MACN,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF;AACE,eAAO;AAAA,IAAA;AAGX,WAAO,IAAI,aAAa,SAAS,MAAM,YAAY,QAAQ;AAAA,EAC7D;AAAA,EAEA,OAAO,aAAa,SAAiB,OAA6B;AAChE,WAAO,IAAI;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,cAAuB;AACrB,WACE,KAAK,SAAS,kBACd,KAAK,SAAS;AAAA,EAElB;AACF;AC7DA,SAAS,kBAAkB,OAAmC;AAC5D,SACE,UAAU,QACV,OAAO,UAAU,YACjB,gBAAgB,SAChB,aAAa,SACb,sBAAsB,SACtB,OAAQ,MAAmB,eAAe;AAE9C;AAsCA,eAAsB,WACpB,SACmB;AAEnB,MAAI,kBAAkB,OAAO,GAAG;AAC9B,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,QAAQ,MAAM;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,iBAAiB;AAAA,IAAA;AAAA,EAErB;AAEA,MAAI,CAAC,QAAQ,WAAW;AACtB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,iBAAiB;AAAA,IAAA;AAAA,EAErB;AAEA,MAAI,CAAC,QAAQ,OAAO;AAClB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,iBAAiB;AAAA,IAAA;AAAA,EAErB;AAGA,UAAQ,QAAQ,MAAA;AAAA,IACd,KAAK,UAAU;AACb,YAAM,EAAE,eAAAC,eAAA,IAAkB,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,KAAA;AAChC,aAAO,IAAIA,eAAc,OAAO;AAAA,IAClC;AAAA,IAEA,KAAK;AACH,YAAM,IAAI;AAAA,QACR;AAAA,QACA,iBAAiB;AAAA,MAAA;AAAA,IAGrB,KAAK;AACH,YAAM,IAAI;AAAA,QACR;AAAA,QACA,iBAAiB;AAAA,MAAA;AAAA,IAGrB,KAAK;AACH,YAAM,IAAI;AAAA,QACR;AAAA,QACA,iBAAiB;AAAA,MAAA;AAAA,IAGrB;AACE,YAAM,IAAI;AAAA,QACR,6BAA6B,QAAQ,IAAI;AAAA,QACzC,iBAAiB;AAAA,MAAA;AAAA,EACnB;AAEN;ACrGO,MAAM,cAAkC;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAuB;AACjC,QAAI,OAAO,SAAS,UAAU;AAC5B,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,SAAK,UAAU,IAAI,cAAc;AAAA,MAC/B,UAAU;AAAA,MACV,OAAO,OAAO;AAAA,IAAA,CACf;AACD,SAAK,YAAY,OAAO;AACxB,SAAK,QAAQ,OAAO;AACpB,SAAK,OAAO,OAAO;AACnB,SAAK,gBAAgB,OAAO;AAC5B,SAAK,gBAAgB,OAAO;AAAA,EAC9B;AAAA;AAAA,EAGA,MAAM,aAA+B;AACnC,UAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyCd,UAAM,OAAQ,MAAM,KAAK,QAAQ,MAAM,OAAO;AAAA,MAC5C,WAAW,KAAK;AAAA,IAAA,CACjB;AAuBD,UAAM,cAAc,KAAK,KAAK,OAAO,MAAM;AAAA,MACzC,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE;AAAA,IAAA;AAGlC,WAAO;AAAA,MACL,IAAI,KAAK,KAAK;AAAA,MACd,OAAO,KAAK,KAAK;AAAA,MACjB,aAAa,KAAK,KAAK;AAAA,MACvB,OAAO,KAAK,KAAK,MAAM;AAAA,MACvB,KAAK,KAAK,KAAK;AAAA,MACf,UAAU,aAAa,UACnB,YAAY,QAAQ,IAAI,CAAC,KAAKC,YAAW;AAAA,QACvC,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,aAAa,IAAI;AAAA,QACjB,OAAO,IAAI;AAAA,QACX,OAAOA;AAAA,MAAA,EACP,IACF,CAAA;AAAA,MACJ,QAAQ,KAAK,KAAK,OAAO,MAAM,IAAI,CAAC,OAAO;AAAA,QACzC,IAAI,EAAE;AAAA,QACN,MAAM,EAAE;AAAA,QACR,MAAM,KAAK,aAAa,EAAE,QAAQ;AAAA,QAClC,SAAS,EAAE,SAAS,IAAI,CAAC,SAAS;AAAA,UAChC,IAAI,IAAI;AAAA,UACR,MAAM,IAAI;AAAA,UACV,aAAa,IAAI;AAAA,UACjB,OAAO,IAAI;AAAA,QAAA,EACX;AAAA,MAAA,EACF;AAAA,IAAA;AAAA,EAEN;AAAA;AAAA,EAGA,MAAM,QAAQ,WAAyC;AACrD,UAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBjB,UAAM,OAAQ,MAAM,KAAK,QAAQ,OAAO,UAAU;AAAA,MAChD,WAAW,KAAK;AAAA,MAChB;AAAA,IAAA,CACD;AAWD,WAAO;AAAA,MACL,IAAI,KAAK,qBAAqB,KAAK;AAAA,MACnC,WAAW,KAAK,qBAAqB,KAAK,QAAQ;AAAA,MAClD,QAAQ,CAAA;AAAA,MACR,MAAM;AAAA,IAAA;AAAA,EAEV;AAAA,EAEA,MAAM,WAAW,QAA+B;AAC9C,UAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWjB,UAAM,KAAK,QAAQ,OAAO,UAAU;AAAA,MAClC,WAAW,KAAK;AAAA,MAChB;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEA,MAAM,QAAQ,QAA6C;AACzD,UAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBd,UAAM,OAAQ,MAAM,KAAK,QAAQ,MAAM,OAAO,EAAE,QAAQ;AASxD,QAAI,CAAC,KAAK,MAAM;AACd,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,IAAI,KAAK,KAAK;AAAA,MACd,WAAW,KAAK,KAAK,QAAQ;AAAA,MAC7B,QAAQ,CAAA;AAAA,MACR,MAAM;AAAA,IAAA;AAAA,EAEV;AAAA,EAEA,MAAM,UAAU,SAA+C;AAC7D,UAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0Bd,UAAM,OAAQ,MAAM,KAAK,QAAQ,MAAM,OAAO;AAAA,MAC5C,WAAW,KAAK;AAAA,MAChB,OAAO,SAAS,SAAS;AAAA,MACzB,OAAO,SAAS;AAAA,IAAA,CACjB;AAaD,WAAO,KAAK,KAAK,MAAM,MAAM,IAAI,CAAC,UAAU;AAAA,MAC1C,IAAI,KAAK;AAAA,MACT,WAAW,KAAK,QAAQ;AAAA,MACxB,QAAQ,CAAA;AAAA,MACR,MAAM;AAAA,IAAA,EACN;AAAA,EACJ;AAAA;AAAA,EAGA,MAAM,iBAAiB,QAAgB,QAA+B;AACpE,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,eAAe;AAC9C,YAAM,IAAI;AAAA,QACR;AAAA,QACA,iBAAiB;AAAA,MAAA;AAAA,IAErB;AAEA,UAAM,iBAAiB,KAAK,cAAc,MAAM;AAChD,QAAI,CAAC,gBAAgB;AACnB,YAAM,IAAI;AAAA,QACR,WAAW,MAAM,oDAAoD,OAAO,KAAK,KAAK,aAAa,EAAE,KAAK,IAAI,CAAC;AAAA,QAC/G,iBAAiB;AAAA,MAAA;AAAA,IAErB;AAEA,UAAM,KAAK,gBAAgB,QAAQ,KAAK,eAAe,cAAc;AAAA,EACvE;AAAA,EAEA,MAAM,eAAkC;AACtC,UAAM,UAAU,MAAM,KAAK,WAAA;AAC3B,WAAO,QAAQ;AAAA,EACjB;AAAA;AAAA,EAGA,MAAM,gBACJ,QACA,SACA,OACe;AACf,UAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAejB,UAAM,KAAK,QAAQ,OAAO,UAAU;AAAA,MAClC,WAAW,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,MACA,OAAO,EAAE,sBAAsB,MAAA;AAAA,IAAM,CACtC;AAAA,EACH;AAAA,EAEA,MAAM,aAA+B;AACnC,UAAM,UAAU,MAAM,KAAK,WAAA;AAC3B,WAAO,QAAQ;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKQ,aACN,UAC4D;AAC5D,YAAQ,SAAS,eAAY;AAAA,MAC3B,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IAAA;AAAA,EAEb;AACF;;;;;AC/SO,MAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/errors.ts","../src/factory.ts","../src/github/index.ts","../src/types.ts"],"sourcesContent":["/**\n * Project error handling\n */\n\nexport enum ProjectErrorCode {\n NOT_FOUND = 'NOT_FOUND',\n UNAUTHORIZED = 'UNAUTHORIZED',\n INVALID_STATUS = 'INVALID_STATUS',\n INVALID_FIELD = 'INVALID_FIELD',\n ITEM_NOT_IN_PROJECT = 'ITEM_NOT_IN_PROJECT',\n RATE_LIMITED = 'RATE_LIMITED',\n NETWORK_ERROR = 'NETWORK_ERROR',\n VALIDATION_ERROR = 'VALIDATION_ERROR',\n UNKNOWN = 'UNKNOWN',\n}\n\nexport class ProjectError extends Error {\n constructor(\n message: string,\n public code: ProjectErrorCode,\n public statusCode?: number,\n public response?: unknown,\n ) {\n super(message);\n this.name = 'ProjectError';\n Error.captureStackTrace(this, this.constructor);\n }\n\n static fromHTTPStatus(\n statusCode: number,\n message: string,\n response?: unknown,\n ): ProjectError {\n let code: ProjectErrorCode;\n\n switch (statusCode) {\n case 404:\n code = ProjectErrorCode.NOT_FOUND;\n break;\n case 401:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 403:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 429:\n code = ProjectErrorCode.RATE_LIMITED;\n break;\n case 422:\n code = ProjectErrorCode.VALIDATION_ERROR;\n break;\n default:\n code = ProjectErrorCode.UNKNOWN;\n }\n\n return new ProjectError(message, code, statusCode, response);\n }\n\n static networkError(message: string, cause?: Error): ProjectError {\n return new ProjectError(\n message,\n ProjectErrorCode.NETWORK_ERROR,\n undefined,\n cause,\n );\n }\n\n isRetryable(): boolean {\n return (\n this.code === ProjectErrorCode.RATE_LIMITED ||\n this.code === ProjectErrorCode.NETWORK_ERROR\n );\n }\n}\n","/**\n * Project factory function\n *\n * Creates project instances following the pattern from @happyvertical/files and @happyvertical/sql\n */\n\nimport { ProjectError, ProjectErrorCode } from './errors.js';\nimport type { IProject, ProjectConfig } from './types.js';\n\n/**\n * Check if value is already a project instance\n */\nfunction isProjectInstance(value: unknown): value is IProject {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'getProject' in value &&\n 'addItem' in value &&\n 'updateItemStatus' in value &&\n typeof (value as IProject).getProject === 'function'\n );\n}\n\n/**\n * Get a project management client instance\n *\n * This is the main entry point following the pattern from @happyvertical/files and @happyvertical/sql.\n * It can accept either a configuration object or an existing project instance.\n *\n * @param options - Project configuration or existing project instance\n * @returns Promise resolving to project interface\n *\n * @example\n * ```typescript\n * import { getProject } from '@happyvertical/projects';\n *\n * // Create a GitHub Projects client\n * const project = await getProject({\n * type: 'github',\n * owner: 'happyvertical',\n * projectId: 'PVT_kwDOB9Y8ns4A8-TY',\n * token: process.env.GITHUB_TOKEN,\n * statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',\n * statusOptions: {\n * 'New': 'option-id-1',\n * 'Backlog': 'option-id-2',\n * 'In Progress': 'option-id-3',\n * 'Done': 'option-id-4'\n * }\n * });\n *\n * // Use the client\n * await project.addItem(issueNodeId);\n * await project.updateItemStatus(itemId, 'In Progress');\n *\n * // Pass existing instance (returns it unchanged)\n * const sameProject = await getProject(project);\n * ```\n */\nexport async function getProject(\n options: ProjectConfig | IProject,\n): Promise<IProject> {\n // If already a project instance, return it\n if (isProjectInstance(options)) {\n return options;\n }\n\n // Validate configuration\n if (!options.type) {\n throw new ProjectError(\n 'Project type is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.projectId) {\n throw new ProjectError(\n 'Project ID is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.token) {\n throw new ProjectError(\n 'Authentication token is required',\n ProjectErrorCode.UNAUTHORIZED,\n );\n }\n\n // Create project instance based on type\n switch (options.type) {\n case 'github': {\n const { GitHubProject } = await import('./github/index.js');\n return new GitHubProject(options);\n }\n\n case 'jira':\n throw new ProjectError(\n 'Jira support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'zenhub':\n throw new ProjectError(\n 'ZenHub support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'linear':\n throw new ProjectError(\n 'Linear support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n default:\n throw new ProjectError(\n `Unsupported project type: ${options.type}`,\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n}\n","/**\n * GitHub Projects V2 implementation\n */\n\nimport { GraphQLClient, type IGraphQLClient } from '@happyvertical/graphql';\nimport { ProjectError, ProjectErrorCode } from '../errors.js';\nimport type {\n Field,\n IProject,\n ItemFilters,\n Project,\n ProjectConfig,\n ProjectItem,\n Status,\n} from '../types.js';\n\nconst GITHUB_DEFAULT_ITEMS_LIMIT = 100;\nconst GITHUB_ITEMS_PAGE_SIZE = 20;\n\ninterface GitHubProjectItemReference {\n typename: ProjectItem['type'];\n id: string;\n}\n\ninterface GitHubProjectItemContent extends GitHubProjectItemReference {\n title: string;\n url?: string;\n assignees: {\n nodes: Array<{ login: string } | null>;\n };\n}\n\ninterface GitHubProjectFieldValue {\n typename: string;\n field?: {\n id: string;\n name: string;\n };\n date?: string | null;\n title?: string;\n labels?: {\n nodes: Array<{ name: string } | null>;\n } | null;\n milestone?: {\n title: string;\n } | null;\n number?: number | null;\n pullRequests?: {\n nodes: Array<{ url: string } | null>;\n } | null;\n repository?: {\n nameWithOwner: string;\n } | null;\n reviewers?: {\n nodes: Array<{\n login?: string;\n slug?: string;\n } | null>;\n } | null;\n name?: string | null;\n text?: string | null;\n users?: {\n nodes: Array<{ login: string } | null>;\n } | null;\n issueFieldValue?: {\n typename: string;\n name?: string;\n dateValue?: string;\n multiSelectOptions?: Array<{ name: string }>;\n numberValue?: number;\n singleSelectValue?: string;\n textValue?: string;\n } | null;\n}\n\ninterface GitHubProjectItemNode {\n id: string;\n content: GitHubProjectItemContent | null;\n fieldValues: {\n nodes: Array<GitHubProjectFieldValue | null>;\n };\n}\n\ninterface GitHubProjectItemsPage {\n node: {\n items: {\n nodes: Array<GitHubProjectItemNode | null>;\n pageInfo: {\n hasNextPage: boolean;\n endCursor: string | null;\n };\n };\n } | null;\n}\n\n/**\n * GitHub Projects V2 implementation\n */\nexport class GitHubProject implements IProject {\n private graphql: IGraphQLClient;\n private projectId: string;\n private owner?: string;\n private repo?: string;\n private statusFieldId?: string;\n private statusOptions?: Record<string, string>;\n\n constructor(config: ProjectConfig) {\n if (config.type !== 'github') {\n throw new Error('Invalid config type for GitHubProject');\n }\n\n this.graphql = new GraphQLClient({\n endpoint: 'https://api.github.com/graphql',\n token: config.token,\n });\n this.projectId = config.projectId;\n this.owner = config.owner;\n this.repo = config.repo;\n this.statusFieldId = config.statusFieldId;\n this.statusOptions = config.statusOptions;\n }\n\n // Project Info\n async getProject(): Promise<Project> {\n const query = `\n query($projectId: ID!) {\n node(id: $projectId) {\n ... on ProjectV2 {\n id\n title\n shortDescription\n url\n owner {\n ... on Organization {\n login\n }\n ... on User {\n login\n }\n }\n fields(first: 20) {\n nodes {\n ... on ProjectV2Field {\n id\n name\n dataType\n }\n ... on ProjectV2SingleSelectField {\n id\n name\n dataType\n options {\n id\n name\n description\n color\n }\n }\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, {\n projectId: this.projectId,\n })) as {\n node: {\n id: string;\n title: string;\n shortDescription: string;\n url: string;\n owner: { login: string };\n fields: {\n nodes: Array<{\n id: string;\n name: string;\n dataType: string;\n options?: Array<{\n id: string;\n name: string;\n description?: string;\n color?: string;\n }>;\n }>;\n };\n };\n };\n\n const statusField = data.node.fields.nodes.find(\n (f) => f.name === 'Status' && f.options,\n );\n\n return {\n id: data.node.id,\n title: data.node.title,\n description: data.node.shortDescription,\n owner: data.node.owner.login,\n url: data.node.url,\n statuses: statusField?.options\n ? statusField.options.map((opt, index) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n order: index,\n }))\n : [],\n fields: data.node.fields.nodes.map((f) => ({\n id: f.id,\n name: f.name,\n type: this.mapFieldType(f.dataType),\n options: f.options?.map((opt) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n })),\n })),\n };\n }\n\n // Items\n /**\n * Add an issue or pull request to the configured GitHub project.\n *\n * The mutation result contains item identity and content type only. Use\n * {@link listItems} when content metadata, status, or fields are required.\n *\n * @param contentId - GitHub node ID for the issue or pull request\n * @returns The created provider-neutral project item\n */\n async addItem(contentId: string): Promise<ProjectItem> {\n const mutation = `\n mutation($projectId: ID!, $contentId: ID!) {\n addProjectV2ItemById(input: {\n projectId: $projectId\n contentId: $contentId\n }) {\n item {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n }\n ... on PullRequest {\n id\n typename: __typename\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n contentId,\n })) as {\n addProjectV2ItemById: {\n item: {\n id: string;\n content: GitHubProjectItemReference;\n };\n };\n };\n\n return {\n id: data.addProjectV2ItemById.item.id,\n contentId: data.addProjectV2ItemById.item.content.id,\n fields: {},\n type: data.addProjectV2ItemById.item.content.typename,\n };\n }\n\n async removeItem(itemId: string): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!) {\n deleteProjectV2Item(input: {\n projectId: $projectId\n itemId: $itemId\n }) {\n deletedItemId\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n });\n }\n\n /**\n * Get one GitHub Projects V2 item by its project-item ID.\n *\n * This identity lookup does not hydrate content metadata, status, or fields.\n * Use {@link listItems} for the enriched board-item representation.\n *\n * @param itemId - GitHub project-item node ID\n * @returns The item, or `null` when it or its content is inaccessible\n */\n async getItem(itemId: string): Promise<ProjectItem | null> {\n const query = `\n query($itemId: ID!) {\n node(id: $itemId) {\n ... on ProjectV2Item {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n }\n ... on PullRequest {\n id\n typename: __typename\n }\n ... on DraftIssue {\n id\n typename: __typename\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, { itemId })) as {\n node: {\n id: string;\n content: Partial<GitHubProjectItemReference> | null;\n } | null;\n };\n\n if (!data.node?.content?.id || !data.node.content.typename) {\n return null;\n }\n\n return {\n id: data.node.id,\n contentId: data.node.content.id,\n fields: {},\n type: data.node.content.typename,\n };\n }\n\n /**\n * List GitHub Projects V2 items after an optional cursor.\n *\n * Deleted or inaccessible content is skipped, and the result defaults to at\n * most 100 accessible items to preserve the adapter's historical limit.\n *\n * @param filters - Optional cursor and maximum result count\n * @returns Provider-neutral project items with content metadata and fields\n */\n async listItems(filters?: ItemFilters): Promise<ProjectItem[]> {\n const query = `\n query($projectId: ID!, $first: Int!, $after: String) {\n node(id: $projectId) {\n ... on ProjectV2 {\n items(first: $first, after: $after) {\n nodes {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n title\n url\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on PullRequest {\n id\n typename: __typename\n title\n url\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on DraftIssue {\n id\n typename: __typename\n title\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n }\n fieldValues(first: 50) {\n nodes {\n typename: __typename\n # GitHub Projects have at most 50 fields. A 20-item page and\n # four 100-node nested connections stay below GitHub's\n # 500,000-node query limit while avoiding partial values.\n ... on ProjectV2ItemFieldDateValue {\n field {\n ...ProjectField\n }\n date\n }\n ... on ProjectV2ItemFieldIterationValue {\n field {\n ...ProjectField\n }\n title\n }\n ... on ProjectV2ItemFieldLabelValue {\n field {\n ...ProjectField\n }\n labels(first: 100) {\n nodes {\n name\n }\n }\n }\n ... on ProjectV2ItemFieldMilestoneValue {\n field {\n ...ProjectField\n }\n milestone {\n title\n }\n }\n ... on ProjectV2ItemFieldNumberValue {\n field {\n ...ProjectField\n }\n number\n }\n ... on ProjectV2ItemFieldPullRequestValue {\n field {\n ...ProjectField\n }\n pullRequests(first: 100) {\n nodes {\n url\n }\n }\n }\n ... on ProjectV2ItemFieldRepositoryValue {\n field {\n ...ProjectField\n }\n repository {\n nameWithOwner\n }\n }\n ... on ProjectV2ItemFieldReviewerValue {\n field {\n ...ProjectField\n }\n reviewers(first: 100) {\n nodes {\n ... on Bot {\n login\n }\n ... on EnterpriseTeam {\n slug\n }\n ... on Mannequin {\n login\n }\n ... on Team {\n slug\n }\n ... on User {\n login\n }\n }\n }\n }\n ... on ProjectV2ItemFieldSingleSelectValue {\n field {\n ...ProjectField\n }\n name\n }\n ... on ProjectV2ItemFieldTextValue {\n field {\n ...ProjectField\n }\n text\n }\n ... on ProjectV2ItemFieldUserValue {\n field {\n ...ProjectField\n }\n users(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on ProjectV2ItemIssueFieldValue {\n field {\n ...ProjectField\n }\n issueFieldValue {\n typename: __typename\n ... on IssueFieldDateValue {\n dateValue: value\n }\n ... on IssueFieldMultiSelectValue {\n multiSelectOptions: options {\n name\n }\n }\n ... on IssueFieldNumberValue {\n numberValue: value\n }\n ... on IssueFieldSingleSelectValue {\n name\n singleSelectValue: value\n }\n ... on IssueFieldTextValue {\n textValue: value\n }\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n }\n }\n\n fragment ProjectField on ProjectV2FieldCommon {\n id\n name\n }\n `;\n\n const requestedLimit = filters?.limit ?? GITHUB_DEFAULT_ITEMS_LIMIT;\n const limit = Number.isFinite(requestedLimit)\n ? Math.max(0, Math.floor(requestedLimit))\n : GITHUB_DEFAULT_ITEMS_LIMIT;\n if (limit === 0) {\n return [];\n }\n\n const items: ProjectItem[] = [];\n let after = filters?.cursor;\n let fillPage = false;\n const seenCursors = new Set<string>();\n\n while (items.length < limit) {\n const remaining = limit - items.length;\n const first = fillPage\n ? GITHUB_ITEMS_PAGE_SIZE\n : Math.min(GITHUB_ITEMS_PAGE_SIZE, remaining);\n const data = await this.graphql.query<GitHubProjectItemsPage>(query, {\n projectId: this.projectId,\n first,\n after,\n });\n if (!data.node) {\n throw new ProjectError(\n `GitHub project ${this.projectId} was not found or is inaccessible`,\n ProjectErrorCode.NOT_FOUND,\n );\n }\n const connection = data.node.items;\n let skippedContent = false;\n\n for (const node of connection.nodes) {\n if (!node) {\n skippedContent = true;\n continue;\n }\n const item = this.mapProjectItem(node);\n if (item) {\n items.push(item);\n } else {\n skippedContent = true;\n }\n if (items.length >= limit) {\n break;\n }\n }\n fillPage = skippedContent;\n\n const { endCursor, hasNextPage } = connection.pageInfo;\n if (\n !hasNextPage ||\n !endCursor ||\n endCursor === after ||\n seenCursors.has(endCursor)\n ) {\n break;\n }\n seenCursors.add(endCursor);\n after = endCursor;\n }\n\n return items;\n }\n\n /**\n * Convert one GitHub Projects V2 item into the provider-neutral item shape.\n */\n private mapProjectItem(item: GitHubProjectItemNode): ProjectItem | null {\n const { content } = item;\n if (!content) {\n return null;\n }\n\n const fieldEntries: Array<[string, unknown]> = [];\n let status: string | undefined;\n\n for (const fieldValue of item.fieldValues.nodes) {\n if (!fieldValue?.field) {\n continue;\n }\n const value = this.mapProjectFieldValue(fieldValue);\n if (value !== undefined) {\n fieldEntries.push([fieldValue.field.name, value]);\n }\n\n const isStatusField = this.statusFieldId\n ? fieldValue.field.id === this.statusFieldId\n : fieldValue.field.name.toLowerCase() === 'status';\n if (isStatusField && typeof value === 'string') {\n status = value;\n }\n }\n\n return {\n id: item.id,\n contentId: content.id,\n title: content.title,\n url: content.url ?? null,\n assignees: content.assignees.nodes.flatMap((assignee) =>\n assignee ? [assignee.login] : [],\n ),\n status,\n fields: Object.fromEntries(fieldEntries),\n type: content.typename,\n };\n }\n\n /**\n * Convert GitHub's field-value union into stable scalar or string-array values.\n */\n private mapProjectFieldValue(value: GitHubProjectFieldValue): unknown {\n switch (value.typename) {\n case 'ProjectV2ItemFieldDateValue':\n return value.date;\n case 'ProjectV2ItemFieldIterationValue':\n return value.title;\n case 'ProjectV2ItemFieldLabelValue':\n return value.labels?.nodes.flatMap((label) =>\n label ? [label.name] : [],\n );\n case 'ProjectV2ItemFieldMilestoneValue':\n return value.milestone?.title;\n case 'ProjectV2ItemFieldNumberValue':\n return value.number;\n case 'ProjectV2ItemFieldPullRequestValue':\n return value.pullRequests?.nodes.flatMap((pullRequest) =>\n pullRequest ? [pullRequest.url] : [],\n );\n case 'ProjectV2ItemFieldRepositoryValue':\n return value.repository?.nameWithOwner;\n case 'ProjectV2ItemFieldReviewerValue':\n return value.reviewers?.nodes.flatMap((reviewer) => {\n const name = reviewer?.login ?? reviewer?.slug;\n return name ? [name] : [];\n });\n case 'ProjectV2ItemFieldSingleSelectValue':\n return value.name;\n case 'ProjectV2ItemFieldTextValue':\n return value.text;\n case 'ProjectV2ItemFieldUserValue':\n return value.users?.nodes.flatMap((user) => (user ? [user.login] : []));\n case 'ProjectV2ItemIssueFieldValue': {\n const issueValue = value.issueFieldValue;\n switch (issueValue?.typename) {\n case 'IssueFieldDateValue':\n return issueValue.dateValue;\n case 'IssueFieldMultiSelectValue':\n return issueValue.multiSelectOptions?.map((option) => option.name);\n case 'IssueFieldNumberValue':\n return issueValue.numberValue;\n case 'IssueFieldSingleSelectValue':\n return issueValue.name ?? issueValue.singleSelectValue;\n case 'IssueFieldTextValue':\n return issueValue.textValue;\n default:\n return undefined;\n }\n }\n default:\n return undefined;\n }\n }\n\n // Status Management\n async updateItemStatus(itemId: string, status: string): Promise<void> {\n if (!this.statusFieldId || !this.statusOptions) {\n throw new ProjectError(\n 'Status field configuration is required. Provide statusFieldId and statusOptions in config.',\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n const statusOptionId = this.statusOptions[status];\n if (!statusOptionId) {\n throw new ProjectError(\n `Status \"${status}\" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(', ')}`,\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n await this.updateItemField(itemId, this.statusFieldId, statusOptionId);\n }\n\n async listStatuses(): Promise<Status[]> {\n const project = await this.getProject();\n return project.statuses;\n }\n\n // Field Management\n async updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {\n updateProjectV2ItemFieldValue(input: {\n projectId: $projectId\n itemId: $itemId\n fieldId: $fieldId\n value: $value\n }) {\n projectV2Item {\n id\n }\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n fieldId,\n value: { singleSelectOptionId: value },\n });\n }\n\n async listFields(): Promise<Field[]> {\n const project = await this.getProject();\n return project.fields;\n }\n\n /**\n * Map GitHub field type to our standard types\n */\n private mapFieldType(\n dataType: string,\n ): 'text' | 'number' | 'date' | 'single_select' | 'iteration' {\n switch (dataType.toLowerCase()) {\n case 'text':\n return 'text';\n case 'number':\n return 'number';\n case 'date':\n return 'date';\n case 'single_select':\n return 'single_select';\n case 'iteration':\n return 'iteration';\n default:\n return 'text';\n }\n }\n}\n","/**\n * Core types for project management operations\n */\n\nexport interface Project {\n id: string;\n title: string;\n description?: string;\n owner: string; // Organization or user\n url: string;\n statuses: Status[];\n fields: Field[];\n}\n\n/**\n * A provider-neutral item on a project board.\n */\nexport interface ProjectItem {\n /** Provider project-item ID. */\n id: string;\n /** Provider content-node ID for the issue, pull request, or draft issue. */\n contentId: string;\n /** Content title when returned by the provider. */\n title?: string;\n /** Content URL, or `null` when the content has no provider URL. */\n url?: string | null;\n /** Provider usernames assigned to the content. */\n assignees?: string[];\n /** Selected status name from the configured or canonical status field. */\n status?: string;\n /** Provider field values keyed by field name. */\n fields: Record<string, unknown>;\n /** Kind of content represented by the project item. */\n type: 'Issue' | 'PullRequest' | 'DraftIssue';\n}\n\nexport interface Status {\n id: string;\n name: string;\n description?: string;\n color?: string;\n order: number;\n}\n\nexport interface Field {\n id: string;\n name: string;\n type: 'text' | 'number' | 'date' | 'single_select' | 'iteration';\n options?: FieldOption[];\n}\n\nexport interface FieldOption {\n id: string;\n name: string;\n description?: string;\n color?: string;\n}\n\n/**\n * Options for reading project items.\n */\nexport interface ItemFilters {\n /** Return items in this status when supported by the provider. */\n status?: string;\n /** Return items assigned to these provider usernames when supported. */\n assignees?: string[];\n /** Return items with these labels when supported. */\n labels?: string[];\n /** Maximum number of items to return. Defaults to 100; zero returns none. */\n limit?: number;\n /** Provider cursor after which item pagination begins. */\n cursor?: string;\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n type: 'github' | 'jira' | 'zenhub' | 'linear';\n projectId: string;\n token: string;\n\n // GitHub Projects V2\n owner?: string; // Organization or user\n repo?: string; // Repository name (for repo-level projects)\n statusFieldId?: string;\n statusOptions?: Record<string, string>; // Map status name to option ID\n\n // Jira\n boardId?: string;\n\n // Linear\n teamId?: string;\n}\n\n/**\n * Kanban status names for 6-lane structure\n */\nexport const KANBAN_STATUSES = [\n 'New',\n 'Backlog',\n 'Planning',\n 'Ready',\n 'In Progress',\n 'Review',\n 'Done',\n] as const;\n\nexport type KanbanStatus = (typeof KANBAN_STATUSES)[number];\n\n/**\n * Project interface - all project implementations must implement this\n */\nexport interface IProject {\n // Project Info\n getProject(): Promise<Project>;\n\n // Items (Issues/Cards)\n addItem(contentId: string): Promise<ProjectItem>;\n removeItem(itemId: string): Promise<void>;\n getItem(itemId: string): Promise<ProjectItem | null>;\n listItems(filters?: ItemFilters): Promise<ProjectItem[]>;\n\n // Status/Column Management\n updateItemStatus(itemId: string, status: string): Promise<void>;\n listStatuses(): Promise<Status[]>;\n\n // Field Management\n updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void>;\n listFields(): Promise<Field[]>;\n}\n"],"names":["ProjectErrorCode","GitHubProject","index"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/projects",
|
|
3
|
-
"version": "0.78.
|
|
3
|
+
"version": "0.78.3",
|
|
4
4
|
"description": "Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"have-projects-context": "./dist/cli/claude-context.js"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@happyvertical/graphql": "0.78.
|
|
19
|
-
"@happyvertical/repos": "0.78.
|
|
18
|
+
"@happyvertical/graphql": "0.78.3",
|
|
19
|
+
"@happyvertical/repos": "0.78.3"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "25.0.10",
|