@enrichlayer/el-linear 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/commands/attachments.js +2 -1
- package/dist/commands/comments.js +17 -28
- package/dist/commands/cycles.js +2 -1
- package/dist/commands/documents.js +2 -1
- package/dist/commands/graphql.js +4 -6
- package/dist/commands/issue-id.js +1 -3
- package/dist/commands/issues/description.js +2 -6
- package/dist/commands/issues/link-references.d.ts +21 -0
- package/dist/commands/issues/link-references.js +171 -0
- package/dist/commands/issues/relations.d.ts +55 -0
- package/dist/commands/issues/relations.js +132 -0
- package/dist/commands/issues.js +65 -284
- package/dist/commands/labels.js +13 -23
- package/dist/commands/project-milestones.js +13 -20
- package/dist/commands/projects.js +67 -38
- package/dist/commands/refs.js +8 -0
- package/dist/commands/releases.js +26 -30
- package/dist/commands/search.js +21 -30
- package/dist/commands/teams.js +2 -1
- package/dist/commands/templates.js +9 -14
- package/dist/commands/users.js +2 -1
- package/dist/config/resolver.js +2 -3
- package/dist/queries/attachments-types.d.ts +30 -0
- package/dist/queries/attachments-types.js +5 -0
- package/dist/queries/comments-types.d.ts +49 -0
- package/dist/queries/comments-types.js +5 -0
- package/dist/queries/documents-types.d.ts +61 -0
- package/dist/queries/documents-types.js +9 -0
- package/dist/queries/introspect-types.d.ts +57 -0
- package/dist/queries/introspect-types.js +10 -0
- package/dist/queries/issues-types.d.ts +416 -0
- package/dist/queries/issues-types.js +23 -0
- package/dist/queries/issues.d.ts +2 -0
- package/dist/queries/issues.js +22 -0
- package/dist/queries/labels-types.d.ts +64 -0
- package/dist/queries/labels-types.js +5 -0
- package/dist/queries/project-milestones-types.d.ts +91 -0
- package/dist/queries/project-milestones-types.js +10 -0
- package/dist/queries/projects-types.d.ts +75 -0
- package/dist/queries/projects-types.js +5 -0
- package/dist/queries/projects.d.ts +2 -0
- package/dist/queries/projects.js +22 -0
- package/dist/queries/releases-types.d.ts +84 -0
- package/dist/queries/releases-types.js +5 -0
- package/dist/queries/search-types.d.ts +86 -0
- package/dist/queries/search-types.js +6 -0
- package/dist/queries/templates-types.d.ts +61 -0
- package/dist/queries/templates-types.js +9 -0
- package/dist/types/linear.d.ts +8 -2
- package/dist/utils/auto-link-references.js +29 -24
- package/dist/utils/graphql-attachments-service.js +6 -9
- package/dist/utils/graphql-documents-service.js +19 -25
- package/dist/utils/graphql-issues-service.d.ts +12 -2
- package/dist/utils/graphql-issues-service.js +151 -172
- package/dist/utils/output.d.ts +2 -0
- package/dist/utils/output.js +16 -6
- package/dist/utils/workspace-url.d.ts +5 -1
- package/dist/utils/workspace-url.js +35 -5
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveUserDisplayName } from "../config/resolver.js";
|
|
2
|
-
import { BATCH_RESOLVE_FOR_CREATE_QUERY, BATCH_RESOLVE_FOR_SEARCH_QUERY, BATCH_RESOLVE_FOR_UPDATE_QUERY, buildResolveLabelsByNameQuery, CREATE_ISSUE_MUTATION, FILTERED_SEARCH_ISSUES_QUERY, GET_ISSUE_BY_ID_QUERY, GET_ISSUE_BY_IDENTIFIER_QUERY, GET_ISSUE_TEAM_QUERY, GET_ISSUES_QUERY, SEARCH_ISSUES_QUERY, UPDATE_ISSUE_MUTATION, } from "../queries/issues.js";
|
|
2
|
+
import { ARCHIVE_ISSUE_MUTATION, BATCH_RESOLVE_FOR_CREATE_QUERY, BATCH_RESOLVE_FOR_SEARCH_QUERY, BATCH_RESOLVE_FOR_UPDATE_QUERY, buildResolveLabelsByNameQuery, CREATE_ISSUE_MUTATION, DELETE_ISSUE_MUTATION, FILTERED_SEARCH_ISSUES_QUERY, GET_ISSUE_BY_ID_QUERY, GET_ISSUE_BY_IDENTIFIER_QUERY, GET_ISSUE_TEAM_QUERY, GET_ISSUES_QUERY, SEARCH_ISSUES_QUERY, UPDATE_ISSUE_MUTATION, } from "../queries/issues.js";
|
|
3
3
|
import { CREATE_LABEL_MUTATION } from "../queries/labels.js";
|
|
4
4
|
import { toISOStringOrNow } from "./date-format.js";
|
|
5
5
|
import { extractEmbeds } from "./embed-parser.js";
|
|
@@ -14,7 +14,7 @@ function extractSummaryText(summary) {
|
|
|
14
14
|
}
|
|
15
15
|
const parts = [];
|
|
16
16
|
const walk = (node) => {
|
|
17
|
-
if (node.text) {
|
|
17
|
+
if (typeof node.text === "string") {
|
|
18
18
|
parts.push(node.text);
|
|
19
19
|
}
|
|
20
20
|
if (Array.isArray(node.content)) {
|
|
@@ -34,15 +34,12 @@ export class GraphQLIssuesService {
|
|
|
34
34
|
this.linearService = linearService;
|
|
35
35
|
}
|
|
36
36
|
async getIssues(limit = 25) {
|
|
37
|
-
const result = await this.graphQLService.rawRequest(GET_ISSUES_QUERY, {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
const issues = result.issues;
|
|
42
|
-
if (!issues?.nodes) {
|
|
37
|
+
const result = await this.graphQLService.rawRequest(GET_ISSUES_QUERY, { first: limit, orderBy: "updatedAt" });
|
|
38
|
+
const nodes = result.issues?.nodes;
|
|
39
|
+
if (!nodes?.length) {
|
|
43
40
|
return [];
|
|
44
41
|
}
|
|
45
|
-
return
|
|
42
|
+
return nodes.map((issue) => this.transformIssueData(issue));
|
|
46
43
|
}
|
|
47
44
|
async getIssueById(issueId) {
|
|
48
45
|
let issueData;
|
|
@@ -55,12 +52,8 @@ export class GraphQLIssuesService {
|
|
|
55
52
|
}
|
|
56
53
|
else {
|
|
57
54
|
const { teamKey, issueNumber } = parseIssueIdentifier(issueId);
|
|
58
|
-
const result = await this.graphQLService.rawRequest(GET_ISSUE_BY_IDENTIFIER_QUERY, {
|
|
59
|
-
|
|
60
|
-
number: issueNumber,
|
|
61
|
-
});
|
|
62
|
-
const issues = result.issues;
|
|
63
|
-
const nodes = issues?.nodes;
|
|
55
|
+
const result = await this.graphQLService.rawRequest(GET_ISSUE_BY_IDENTIFIER_QUERY, { teamKey, number: issueNumber });
|
|
56
|
+
const nodes = result.issues?.nodes;
|
|
64
57
|
if (!nodes?.length) {
|
|
65
58
|
throw notFoundError("Issue", issueId);
|
|
66
59
|
}
|
|
@@ -97,15 +90,40 @@ export class GraphQLIssuesService {
|
|
|
97
90
|
});
|
|
98
91
|
return this.executeUpdateMutation(resolvedIssueId, args.id, updateInput);
|
|
99
92
|
}
|
|
93
|
+
async archiveIssue(issueId) {
|
|
94
|
+
const resolvedIssueId = await this.linearService.resolveIssueId(issueId);
|
|
95
|
+
const result = await this.graphQLService.rawRequest(ARCHIVE_ISSUE_MUTATION, { id: resolvedIssueId });
|
|
96
|
+
const payload = result.issueArchive;
|
|
97
|
+
if (!payload.success) {
|
|
98
|
+
throw new Error(`Failed to archive issue ${issueId}`);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
id: resolvedIssueId,
|
|
102
|
+
entity: payload.entity ?? undefined,
|
|
103
|
+
lastSyncId: payload.lastSyncId,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async deleteIssue(issueId, options = {}) {
|
|
107
|
+
const resolvedIssueId = await this.linearService.resolveIssueId(issueId);
|
|
108
|
+
const result = await this.graphQLService.rawRequest(DELETE_ISSUE_MUTATION, {
|
|
109
|
+
id: resolvedIssueId,
|
|
110
|
+
permanentlyDelete: Boolean(options.permanentlyDelete),
|
|
111
|
+
});
|
|
112
|
+
const payload = result.issueDelete;
|
|
113
|
+
if (!payload.success) {
|
|
114
|
+
throw new Error(`Failed to delete issue ${issueId}`);
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
id: resolvedIssueId,
|
|
118
|
+
entity: payload.entity ?? undefined,
|
|
119
|
+
lastSyncId: payload.lastSyncId,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
100
122
|
extractMilestoneNodes(args, resolveResult) {
|
|
101
|
-
const projectNodes = resolveResult.projects?.nodes;
|
|
102
123
|
const projectMilestoneNodes = args.projectId
|
|
103
|
-
?
|
|
124
|
+
? resolveResult.projects?.nodes?.[0]?.projectMilestones?.nodes
|
|
104
125
|
: undefined;
|
|
105
|
-
const
|
|
106
|
-
?.nodes;
|
|
107
|
-
const issueProjectMilestoneNodes = issueNodes?.[0]?.project
|
|
108
|
-
?.projectMilestones?.nodes;
|
|
126
|
+
const issueProjectMilestoneNodes = resolveResult.issues?.nodes?.[0]?.project?.projectMilestones.nodes;
|
|
109
127
|
return { projectMilestoneNodes, issueProjectMilestoneNodes };
|
|
110
128
|
}
|
|
111
129
|
async executeUpdateMutation(resolvedIssueId, originalId, updateInput) {
|
|
@@ -140,17 +158,17 @@ export class GraphQLIssuesService {
|
|
|
140
158
|
let resolveResult = {};
|
|
141
159
|
if (Object.keys(resolveVariables).length > 0) {
|
|
142
160
|
const { __labelNames, ...queryVars } = resolveVariables;
|
|
143
|
-
const
|
|
144
|
-
this.graphQLService.rawRequest(BATCH_RESOLVE_FOR_CREATE_QUERY, queryVars),
|
|
145
|
-
];
|
|
161
|
+
const batchPromise = this.graphQLService.rawRequest(BATCH_RESOLVE_FOR_CREATE_QUERY, queryVars);
|
|
146
162
|
if (__labelNames) {
|
|
147
163
|
const labelQuery = buildResolveLabelsByNameQuery(__labelNames);
|
|
148
|
-
|
|
164
|
+
const [batch, labels] = await Promise.all([
|
|
165
|
+
batchPromise,
|
|
166
|
+
this.graphQLService.rawRequest(labelQuery.query, labelQuery.variables),
|
|
167
|
+
]);
|
|
168
|
+
resolveResult = { ...batch, labels: labels.labels };
|
|
149
169
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (results[1]) {
|
|
153
|
-
resolveResult.labels = results[1].labels;
|
|
170
|
+
else {
|
|
171
|
+
resolveResult = await batchPromise;
|
|
154
172
|
}
|
|
155
173
|
}
|
|
156
174
|
const resolved = await this.resolveCreateFields(args, resolveResult);
|
|
@@ -231,7 +249,8 @@ export class GraphQLIssuesService {
|
|
|
231
249
|
}
|
|
232
250
|
let resolveResult = {};
|
|
233
251
|
if (Object.keys(resolveVariables).length > 0) {
|
|
234
|
-
resolveResult =
|
|
252
|
+
resolveResult =
|
|
253
|
+
await this.graphQLService.rawRequest(BATCH_RESOLVE_FOR_SEARCH_QUERY, resolveVariables);
|
|
235
254
|
}
|
|
236
255
|
const finalTeamId = args.teamId
|
|
237
256
|
? await this.resolveTeamId(args.teamId, resolveResult)
|
|
@@ -246,11 +265,11 @@ export class GraphQLIssuesService {
|
|
|
246
265
|
term: args.query,
|
|
247
266
|
first: limit,
|
|
248
267
|
});
|
|
249
|
-
const
|
|
250
|
-
if (!
|
|
268
|
+
const nodes = searchResult.searchIssues?.nodes;
|
|
269
|
+
if (!nodes?.length) {
|
|
251
270
|
return [];
|
|
252
271
|
}
|
|
253
|
-
const results =
|
|
272
|
+
const results = nodes.map((issue) => this.transformIssueData(issue));
|
|
254
273
|
return this.applySearchFilters(results, {
|
|
255
274
|
teamId: finalTeamId,
|
|
256
275
|
assigneeId: finalAssigneeId,
|
|
@@ -285,8 +304,7 @@ export class GraphQLIssuesService {
|
|
|
285
304
|
if (isUuid(teamId)) {
|
|
286
305
|
return teamId;
|
|
287
306
|
}
|
|
288
|
-
const
|
|
289
|
-
const teamNodes = teams?.nodes;
|
|
307
|
+
const teamNodes = resolveResult.teams?.nodes;
|
|
290
308
|
const resolvedTeam = teamNodes?.[0];
|
|
291
309
|
if (resolvedTeam &&
|
|
292
310
|
(resolvedTeam.key.toUpperCase() === teamId.toUpperCase() ||
|
|
@@ -300,8 +318,7 @@ export class GraphQLIssuesService {
|
|
|
300
318
|
if (isUuid(projectId)) {
|
|
301
319
|
return projectId;
|
|
302
320
|
}
|
|
303
|
-
const
|
|
304
|
-
const projectNodes = projects?.nodes;
|
|
321
|
+
const projectNodes = resolveResult.projects?.nodes;
|
|
305
322
|
if (!projectNodes?.length) {
|
|
306
323
|
throw notFoundError("Project", projectId);
|
|
307
324
|
}
|
|
@@ -312,13 +329,11 @@ export class GraphQLIssuesService {
|
|
|
312
329
|
* If not, auto-switch when the project belongs to exactly one team.
|
|
313
330
|
*/
|
|
314
331
|
validateProjectTeam(teamId, projectInput, resolveResult) {
|
|
315
|
-
const
|
|
316
|
-
const projectNode = projects?.nodes?.[0];
|
|
332
|
+
const projectNode = resolveResult.projects?.nodes?.[0];
|
|
317
333
|
if (!projectNode) {
|
|
318
334
|
return teamId;
|
|
319
335
|
}
|
|
320
|
-
const
|
|
321
|
-
const teamNodes = teamsConn?.nodes;
|
|
336
|
+
const teamNodes = projectNode.teams?.nodes;
|
|
322
337
|
if (!teamNodes?.length) {
|
|
323
338
|
return teamId;
|
|
324
339
|
}
|
|
@@ -346,8 +361,7 @@ export class GraphQLIssuesService {
|
|
|
346
361
|
return labelIds;
|
|
347
362
|
}
|
|
348
363
|
const resolvedLabels = [];
|
|
349
|
-
const
|
|
350
|
-
const labelNodes = labels?.nodes;
|
|
364
|
+
const labelNodes = resolveResult.labels?.nodes;
|
|
351
365
|
for (const labelIdOrName of labelIds) {
|
|
352
366
|
if (isUuid(labelIdOrName)) {
|
|
353
367
|
resolvedLabels.push(labelIdOrName);
|
|
@@ -357,10 +371,7 @@ export class GraphQLIssuesService {
|
|
|
357
371
|
const candidates = labelNodes?.filter((l) => l.name.toLowerCase() === lowerName);
|
|
358
372
|
let label;
|
|
359
373
|
if (candidates && candidates.length > 1 && teamId) {
|
|
360
|
-
label = candidates.find((l) =>
|
|
361
|
-
const team = l.team;
|
|
362
|
-
return team?.id === teamId;
|
|
363
|
-
});
|
|
374
|
+
label = candidates.find((l) => l.team?.id === teamId);
|
|
364
375
|
}
|
|
365
376
|
if (!label) {
|
|
366
377
|
label = candidates?.[0];
|
|
@@ -402,12 +413,10 @@ export class GraphQLIssuesService {
|
|
|
402
413
|
}
|
|
403
414
|
const result = await this.graphQLService.rawRequest(CREATE_LABEL_MUTATION, { input });
|
|
404
415
|
const created = result.issueLabelCreate;
|
|
405
|
-
if (created
|
|
406
|
-
const
|
|
407
|
-
const teamInfo = labelData.team;
|
|
408
|
-
const teamKey = teamInfo?.key ?? "";
|
|
416
|
+
if (created.success && created.issueLabel) {
|
|
417
|
+
const teamKey = created.issueLabel.team?.key ?? "";
|
|
409
418
|
logger.error(`Auto-created label "${name}" on team ${teamKey}`);
|
|
410
|
-
return
|
|
419
|
+
return created.issueLabel.id;
|
|
411
420
|
}
|
|
412
421
|
return null;
|
|
413
422
|
}
|
|
@@ -435,37 +444,27 @@ export class GraphQLIssuesService {
|
|
|
435
444
|
// Only fall back to first team if there's exactly one (unambiguous).
|
|
436
445
|
// With multiple teams, returning an arbitrary one produces misleading error hints.
|
|
437
446
|
const topLevelNodes = resolveResult.teams?.nodes;
|
|
438
|
-
return topLevelNodes?.length === 1
|
|
439
|
-
? topLevelNodes[0].key
|
|
440
|
-
: undefined;
|
|
447
|
+
return topLevelNodes?.length === 1 ? topLevelNodes[0].key : undefined;
|
|
441
448
|
}
|
|
442
449
|
collectTeamNodes(resolveResult) {
|
|
443
450
|
const nodes = [];
|
|
444
451
|
const seen = new Set();
|
|
445
|
-
const
|
|
446
|
-
const topNodes = topTeams?.nodes;
|
|
452
|
+
const topNodes = resolveResult.teams?.nodes;
|
|
447
453
|
if (topNodes) {
|
|
448
454
|
for (const t of topNodes) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
seen.add(id);
|
|
455
|
+
if (!seen.has(t.id)) {
|
|
456
|
+
seen.add(t.id);
|
|
452
457
|
nodes.push(t);
|
|
453
458
|
}
|
|
454
459
|
}
|
|
455
460
|
}
|
|
456
461
|
// Also check project teams (which are always fetched when --project is used)
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
for (const t of projectTeamNodes) {
|
|
464
|
-
const id = t.id;
|
|
465
|
-
if (!seen.has(id)) {
|
|
466
|
-
seen.add(id);
|
|
467
|
-
nodes.push(t);
|
|
468
|
-
}
|
|
462
|
+
const projectTeamNodes = resolveResult.projects?.nodes?.[0]?.teams?.nodes;
|
|
463
|
+
if (projectTeamNodes) {
|
|
464
|
+
for (const t of projectTeamNodes) {
|
|
465
|
+
if (!seen.has(t.id)) {
|
|
466
|
+
seen.add(t.id);
|
|
467
|
+
nodes.push(t);
|
|
469
468
|
}
|
|
470
469
|
}
|
|
471
470
|
}
|
|
@@ -488,8 +487,7 @@ export class GraphQLIssuesService {
|
|
|
488
487
|
resolved = issueMilestone.id;
|
|
489
488
|
}
|
|
490
489
|
}
|
|
491
|
-
const
|
|
492
|
-
const milestoneNodes = milestones?.nodes;
|
|
490
|
+
const milestoneNodes = resolveResult.milestones?.nodes;
|
|
493
491
|
if (!isUuid(resolved) && milestoneNodes?.length) {
|
|
494
492
|
resolved = milestoneNodes[0].id;
|
|
495
493
|
}
|
|
@@ -503,19 +501,14 @@ export class GraphQLIssuesService {
|
|
|
503
501
|
return milestoneId;
|
|
504
502
|
}
|
|
505
503
|
let resolved;
|
|
506
|
-
const
|
|
507
|
-
const createProjectNodes = createProjects?.nodes;
|
|
508
|
-
const createProjectMilestones = createProjectNodes?.[0]
|
|
509
|
-
?.projectMilestones;
|
|
510
|
-
const createProjectMilestoneNodes = createProjectMilestones?.nodes;
|
|
504
|
+
const createProjectMilestoneNodes = resolveResult.projects?.nodes?.[0]?.projectMilestones?.nodes;
|
|
511
505
|
if (createProjectMilestoneNodes) {
|
|
512
506
|
const projectMilestone = createProjectMilestoneNodes.find((m) => m.name === milestoneId);
|
|
513
507
|
if (projectMilestone) {
|
|
514
508
|
resolved = projectMilestone.id;
|
|
515
509
|
}
|
|
516
510
|
}
|
|
517
|
-
const
|
|
518
|
-
const createMilestoneNodes = createMilestones?.nodes;
|
|
511
|
+
const createMilestoneNodes = resolveResult.milestones?.nodes;
|
|
519
512
|
if (!resolved && createMilestoneNodes?.length) {
|
|
520
513
|
resolved = createMilestoneNodes[0].id;
|
|
521
514
|
}
|
|
@@ -528,12 +521,8 @@ export class GraphQLIssuesService {
|
|
|
528
521
|
return resolved;
|
|
529
522
|
}
|
|
530
523
|
async fetchIssueTeamId(issueId) {
|
|
531
|
-
const result = await this.graphQLService.rawRequest(GET_ISSUE_TEAM_QUERY, {
|
|
532
|
-
|
|
533
|
-
});
|
|
534
|
-
const fetchedIssue = result.issue;
|
|
535
|
-
const fetchedTeam = fetchedIssue?.team;
|
|
536
|
-
return fetchedTeam?.id;
|
|
524
|
+
const result = await this.graphQLService.rawRequest(GET_ISSUE_TEAM_QUERY, { issueId });
|
|
525
|
+
return result.issue?.team?.id;
|
|
537
526
|
}
|
|
538
527
|
buildCreateInput(args, resolved) {
|
|
539
528
|
const input = {};
|
|
@@ -704,8 +693,7 @@ export class GraphQLIssuesService {
|
|
|
704
693
|
if (!assigneeId || isUuid(assigneeId) || !assigneeId.includes("@")) {
|
|
705
694
|
return assigneeId;
|
|
706
695
|
}
|
|
707
|
-
const
|
|
708
|
-
const userNodes = users?.nodes;
|
|
696
|
+
const userNodes = resolveResult.users?.nodes;
|
|
709
697
|
if (!userNodes?.length) {
|
|
710
698
|
throw notFoundError("User", assigneeId);
|
|
711
699
|
}
|
|
@@ -734,29 +722,27 @@ export class GraphQLIssuesService {
|
|
|
734
722
|
resolveVariables.milestoneName = args.milestoneId;
|
|
735
723
|
}
|
|
736
724
|
const { __labelNames, ...updateQueryVars } = resolveVariables;
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
];
|
|
725
|
+
const batchPromise = this.graphQLService.rawRequest(BATCH_RESOLVE_FOR_UPDATE_QUERY, updateQueryVars);
|
|
726
|
+
let resolveResult;
|
|
740
727
|
if (__labelNames) {
|
|
741
728
|
const labelQuery = buildResolveLabelsByNameQuery(__labelNames);
|
|
742
|
-
|
|
729
|
+
const [batch, labels] = await Promise.all([
|
|
730
|
+
batchPromise,
|
|
731
|
+
this.graphQLService.rawRequest(labelQuery.query, labelQuery.variables),
|
|
732
|
+
]);
|
|
733
|
+
resolveResult = { ...batch, labels: labels.labels };
|
|
743
734
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
if (results[1]) {
|
|
747
|
-
resolveResult.labels = results[1].labels;
|
|
735
|
+
else {
|
|
736
|
+
resolveResult = await batchPromise;
|
|
748
737
|
}
|
|
749
738
|
if (!isUuid(args.id)) {
|
|
750
|
-
const
|
|
751
|
-
const resolvedIssueNodes = resolvedIssues.nodes;
|
|
739
|
+
const resolvedIssueNodes = resolveResult.issues?.nodes ?? [];
|
|
752
740
|
if (!resolvedIssueNodes.length) {
|
|
753
741
|
throw notFoundError("Issue", args.id);
|
|
754
742
|
}
|
|
755
743
|
resolvedIssueId = resolvedIssueNodes[0].id;
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const issueLabels = resolvedIssueNodes[0].labels;
|
|
759
|
-
currentIssueLabels = issueLabels.nodes.map((l) => l.id);
|
|
744
|
+
issueTeamId = resolvedIssueNodes[0].team?.id;
|
|
745
|
+
currentIssueLabels = resolvedIssueNodes[0].labels.nodes.map((l) => l.id);
|
|
760
746
|
}
|
|
761
747
|
return { resolvedIssueId, issueTeamId, currentIssueLabels, resolveResult };
|
|
762
748
|
}
|
|
@@ -813,30 +799,32 @@ export class GraphQLIssuesService {
|
|
|
813
799
|
if (!parentId || isUuid(parentId)) {
|
|
814
800
|
return parentId;
|
|
815
801
|
}
|
|
816
|
-
const
|
|
817
|
-
const parentNodes = parentIssues?.nodes;
|
|
802
|
+
const parentNodes = resolveResult.parentIssues?.nodes;
|
|
818
803
|
if (!parentNodes?.length) {
|
|
819
804
|
throw notFoundError("Parent issue", parentId);
|
|
820
805
|
}
|
|
821
806
|
return parentNodes[0].id;
|
|
822
807
|
}
|
|
823
808
|
transformIssueData(issue) {
|
|
824
|
-
|
|
809
|
+
// `labels`/`children`/`comments` are connection fields that the
|
|
810
|
+
// fragment always selects, so production responses populate them.
|
|
811
|
+
// Test mocks pass partial objects, so read defensively to keep
|
|
812
|
+
// those test fixtures small.
|
|
825
813
|
return {
|
|
826
814
|
...this.transformIssueCoreFields(issue),
|
|
827
815
|
...this.transformIssueRelations(issue),
|
|
828
|
-
summary: issue.summary
|
|
829
|
-
? extractSummaryText(issue.summary)
|
|
830
|
-
: undefined,
|
|
816
|
+
summary: issue.summary ? extractSummaryText(issue.summary) : undefined,
|
|
831
817
|
priority: issue.priority,
|
|
832
|
-
estimate: issue.estimate
|
|
833
|
-
dueDate: issue.dueDate
|
|
834
|
-
labels: labels
|
|
818
|
+
estimate: issue.estimate ?? undefined,
|
|
819
|
+
dueDate: issue.dueDate ?? undefined,
|
|
820
|
+
labels: (issue.labels?.nodes ?? []).map((label) => ({
|
|
835
821
|
id: label.id,
|
|
836
822
|
name: label.name,
|
|
837
823
|
})),
|
|
838
824
|
...this.transformIssueHierarchy(issue),
|
|
839
|
-
comments:
|
|
825
|
+
comments: "comments" in issue && issue.comments
|
|
826
|
+
? this.transformIssueComments(issue)
|
|
827
|
+
: [],
|
|
840
828
|
createdAt: toISOStringOrNow(issue.createdAt),
|
|
841
829
|
updatedAt: toISOStringOrNow(issue.updatedAt),
|
|
842
830
|
};
|
|
@@ -847,93 +835,84 @@ export class GraphQLIssuesService {
|
|
|
847
835
|
identifier: issue.identifier,
|
|
848
836
|
url: issue.url,
|
|
849
837
|
title: issue.title,
|
|
850
|
-
description: issue.description
|
|
838
|
+
description: issue.description ?? undefined,
|
|
851
839
|
branchName: issue.branchName || undefined,
|
|
852
|
-
embeds: issue.description
|
|
853
|
-
? extractEmbeds(issue.description)
|
|
854
|
-
: undefined,
|
|
840
|
+
embeds: issue.description ? extractEmbeds(issue.description) : undefined,
|
|
855
841
|
};
|
|
856
842
|
}
|
|
857
843
|
transformIssueRelations(issue) {
|
|
858
|
-
const state = issue.state;
|
|
859
|
-
const assignee = issue.assignee;
|
|
860
|
-
const team = issue.team;
|
|
861
|
-
const project = issue.project;
|
|
862
|
-
const cycle = issue.cycle;
|
|
863
|
-
const milestone = issue.projectMilestone;
|
|
864
844
|
return {
|
|
865
|
-
state: state
|
|
866
|
-
? { id: state.id, name: state.name }
|
|
845
|
+
state: issue.state
|
|
846
|
+
? { id: issue.state.id, name: issue.state.name }
|
|
867
847
|
: undefined,
|
|
868
|
-
assignee: assignee
|
|
848
|
+
assignee: issue.assignee
|
|
869
849
|
? {
|
|
870
|
-
id: assignee.id,
|
|
871
|
-
name: resolveUserDisplayName(assignee.id, assignee.name),
|
|
872
|
-
url: assignee.url
|
|
850
|
+
id: issue.assignee.id,
|
|
851
|
+
name: resolveUserDisplayName(issue.assignee.id, issue.assignee.name),
|
|
852
|
+
url: issue.assignee.url ?? undefined,
|
|
873
853
|
}
|
|
874
854
|
: undefined,
|
|
875
|
-
team: team
|
|
855
|
+
team: issue.team
|
|
876
856
|
? {
|
|
877
|
-
id: team.id,
|
|
878
|
-
key: team.key,
|
|
879
|
-
name: team.name,
|
|
857
|
+
id: issue.team.id,
|
|
858
|
+
key: issue.team.key,
|
|
859
|
+
name: issue.team.name,
|
|
880
860
|
}
|
|
881
861
|
: undefined,
|
|
882
|
-
project: project
|
|
883
|
-
? { id: project.id, name: project.name }
|
|
862
|
+
project: issue.project
|
|
863
|
+
? { id: issue.project.id, name: issue.project.name }
|
|
884
864
|
: undefined,
|
|
885
|
-
cycle: cycle
|
|
865
|
+
cycle: issue.cycle
|
|
886
866
|
? {
|
|
887
|
-
id: cycle.id,
|
|
888
|
-
name: cycle.name,
|
|
889
|
-
number: cycle.number,
|
|
867
|
+
id: issue.cycle.id,
|
|
868
|
+
name: issue.cycle.name ?? "",
|
|
869
|
+
number: issue.cycle.number,
|
|
890
870
|
}
|
|
891
871
|
: undefined,
|
|
892
|
-
projectMilestone:
|
|
872
|
+
projectMilestone: issue.projectMilestone
|
|
893
873
|
? {
|
|
894
|
-
id:
|
|
895
|
-
name:
|
|
896
|
-
targetDate:
|
|
874
|
+
id: issue.projectMilestone.id,
|
|
875
|
+
name: issue.projectMilestone.name,
|
|
876
|
+
targetDate: issue.projectMilestone.targetDate ?? undefined,
|
|
897
877
|
}
|
|
898
878
|
: undefined,
|
|
899
879
|
};
|
|
900
880
|
}
|
|
901
881
|
transformIssueHierarchy(issue) {
|
|
902
|
-
|
|
903
|
-
|
|
882
|
+
// children is a connection field — always present in production
|
|
883
|
+
// responses. Defensive read keeps test mocks small.
|
|
884
|
+
const childNodes = issue.children?.nodes ?? [];
|
|
904
885
|
return {
|
|
905
|
-
parentIssue: parent
|
|
886
|
+
parentIssue: issue.parent
|
|
906
887
|
? {
|
|
907
|
-
id: parent.id,
|
|
908
|
-
identifier: parent.identifier,
|
|
909
|
-
title: parent.title,
|
|
888
|
+
id: issue.parent.id,
|
|
889
|
+
identifier: issue.parent.identifier,
|
|
890
|
+
title: issue.parent.title,
|
|
910
891
|
}
|
|
911
892
|
: undefined,
|
|
912
|
-
subIssues:
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
893
|
+
subIssues: childNodes.length > 0
|
|
894
|
+
? childNodes.map((child) => ({
|
|
895
|
+
id: child.id,
|
|
896
|
+
identifier: child.identifier,
|
|
897
|
+
title: child.title,
|
|
898
|
+
}))
|
|
899
|
+
: undefined,
|
|
917
900
|
};
|
|
918
901
|
}
|
|
919
902
|
transformIssueComments(issue) {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
createdAt: toISOStringOrNow(comment.createdAt),
|
|
935
|
-
updatedAt: toISOStringOrNow(comment.updatedAt),
|
|
936
|
-
};
|
|
937
|
-
}) || []);
|
|
903
|
+
return issue.comments.nodes.map((comment) => ({
|
|
904
|
+
id: comment.id,
|
|
905
|
+
body: comment.body,
|
|
906
|
+
embeds: extractEmbeds(comment.body),
|
|
907
|
+
user: comment.user
|
|
908
|
+
? {
|
|
909
|
+
id: comment.user.id,
|
|
910
|
+
name: resolveUserDisplayName(comment.user.id, comment.user.name),
|
|
911
|
+
url: comment.user.url ?? undefined,
|
|
912
|
+
}
|
|
913
|
+
: undefined,
|
|
914
|
+
createdAt: toISOStringOrNow(comment.createdAt),
|
|
915
|
+
updatedAt: toISOStringOrNow(comment.updatedAt),
|
|
916
|
+
}));
|
|
938
917
|
}
|
|
939
918
|
}
|
package/dist/utils/output.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ export declare function setRawMode(enabled: boolean): void;
|
|
|
3
3
|
export declare function setJqFilter(filter: string | null): void;
|
|
4
4
|
export declare function setFieldsFilter(fields: string[] | null): void;
|
|
5
5
|
export declare function setOutputFormat(format: OutputFormat): void;
|
|
6
|
+
/** @internal Test seam — consumers should not depend on the format state. */
|
|
6
7
|
export declare function getOutputFormat(): OutputFormat;
|
|
7
8
|
export declare function outputSuccess(data: unknown): void;
|
|
8
9
|
export declare function outputWarning(message: string | string[]): void;
|
|
9
10
|
export declare function resetWarnings(): void;
|
|
11
|
+
/** @internal Test seam — call between tests that toggle `setOutputFormat`. */
|
|
10
12
|
export declare function resetOutputFormat(): void;
|
|
11
13
|
export declare function handleAsyncCommand<TArgs extends unknown[]>(asyncFn: (...args: TArgs) => Promise<void>): (...args: TArgs) => Promise<void>;
|
package/dist/utils/output.js
CHANGED
|
@@ -18,6 +18,7 @@ export function setFieldsFilter(fields) {
|
|
|
18
18
|
export function setOutputFormat(format) {
|
|
19
19
|
outputFormat = format;
|
|
20
20
|
}
|
|
21
|
+
/** @internal Test seam — consumers should not depend on the format state. */
|
|
21
22
|
export function getOutputFormat() {
|
|
22
23
|
return outputFormat;
|
|
23
24
|
}
|
|
@@ -38,12 +39,15 @@ function filterFields(obj, fields) {
|
|
|
38
39
|
return obj;
|
|
39
40
|
}
|
|
40
41
|
/**
|
|
41
|
-
* Emit a summary-format render to stdout for the given payload.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Emit a summary-format render to stdout for the given payload.
|
|
43
|
+
*
|
|
44
|
+
* `kind` is captured upstream from the **pre-filter** payload — if we
|
|
45
|
+
* inferred here, `--fields identifier,url` would strip `title` and
|
|
46
|
+
* the heuristic would fall through to "generic", silently breaking
|
|
47
|
+
* the issue-list table. Caching the pre-filter shape keeps the
|
|
48
|
+
* formatter accurate regardless of how the user pared the JSON.
|
|
44
49
|
*/
|
|
45
|
-
function emitSummary(payload) {
|
|
46
|
-
const kind = inferKindFromPayload(payload);
|
|
50
|
+
function emitSummary(payload, kind) {
|
|
47
51
|
logger.info(dispatchSummary(kind, payload));
|
|
48
52
|
}
|
|
49
53
|
export function outputSuccess(data) {
|
|
@@ -58,6 +62,11 @@ export function outputSuccess(data) {
|
|
|
58
62
|
else {
|
|
59
63
|
output = data;
|
|
60
64
|
}
|
|
65
|
+
// Capture the kind from the original envelope shape BEFORE --raw /
|
|
66
|
+
// --fields stripping. Otherwise filtering away signature fields (e.g.
|
|
67
|
+
// `title` on an issue) breaks shape inference and the summary
|
|
68
|
+
// formatter falls back to the generic key-value dump.
|
|
69
|
+
const inferredKind = outputFormat === "summary" ? inferKindFromPayload(output) : "generic";
|
|
61
70
|
// --raw: unwrap { data: [...] } to just the array
|
|
62
71
|
if (rawMode &&
|
|
63
72
|
output !== null &&
|
|
@@ -87,7 +96,7 @@ export function outputSuccess(data) {
|
|
|
87
96
|
// as a human-readable block. We bypass the jq path because jq is a
|
|
88
97
|
// JSON-shape filter — it doesn't compose with text output.
|
|
89
98
|
if (outputFormat === "summary") {
|
|
90
|
-
emitSummary(output);
|
|
99
|
+
emitSummary(output, inferredKind);
|
|
91
100
|
return;
|
|
92
101
|
}
|
|
93
102
|
if (jqFilter) {
|
|
@@ -127,6 +136,7 @@ function drainWarnings() {
|
|
|
127
136
|
export function resetWarnings() {
|
|
128
137
|
warningBuffer.length = 0;
|
|
129
138
|
}
|
|
139
|
+
/** @internal Test seam — call between tests that toggle `setOutputFormat`. */
|
|
130
140
|
export function resetOutputFormat() {
|
|
131
141
|
outputFormat = "json";
|
|
132
142
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { GraphQLService } from "./graphql-service.js";
|
|
2
|
-
export
|
|
2
|
+
export interface GetWorkspaceUrlKeyOptions {
|
|
3
|
+
/** Highest-priority source. Typically the `--workspace-url-key` flag. */
|
|
4
|
+
override?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getWorkspaceUrlKey(graphQLService?: GraphQLService, options?: GetWorkspaceUrlKeyOptions): Promise<string>;
|
|
3
7
|
/** Test helper: clear the cached URL key. Not called from production code. */
|
|
4
8
|
export declare function _resetWorkspaceUrlKeyCache(): void;
|