github-issue-tower-defence-management 1.153.5 → 1.154.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/CHANGELOG.md +14 -0
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +187 -6
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/RestIssueRepository.js +6 -0
- package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js +10 -16
- package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js +28 -11
- package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +15 -3
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +14 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +252 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +288 -6
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +19 -0
- package/src/adapter/repositories/issue/RestIssueRepository.ts +13 -0
- package/src/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.test.ts +211 -200
- package/src/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.ts +13 -18
- package/src/domain/usecases/IssueRejectionEvaluator.ts +49 -15
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +154 -0
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +33 -5
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +8 -0
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +9 -2
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/RestIssueRepository.d.ts +1 -0
- package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts +2 -2
- package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts.map +1 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts +6 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
- package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts +1 -1
- package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +3 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
|
@@ -108,6 +108,43 @@ type PrStatusComputationData = {
|
|
|
108
108
|
}>;
|
|
109
109
|
};
|
|
110
110
|
|
|
111
|
+
// One GraphQL query carrying this many aliased pull requests costs the same
|
|
112
|
+
// single rate-limit point as a query carrying one, measured against the live
|
|
113
|
+
// API by reading the rateLimit cost field it returns.
|
|
114
|
+
const SLIM_PULL_REQUEST_BATCH_SIZE = 100;
|
|
115
|
+
const SLIM_PULL_REQUEST_REVIEW_THREADS_PAGE_SIZE = 100;
|
|
116
|
+
|
|
117
|
+
type SlimPullRequestNodeResponse = {
|
|
118
|
+
url: string;
|
|
119
|
+
state: string;
|
|
120
|
+
isDraft?: boolean;
|
|
121
|
+
headRefName?: string;
|
|
122
|
+
baseRefName?: string;
|
|
123
|
+
mergeable?: string;
|
|
124
|
+
headRefOid?: string;
|
|
125
|
+
reviewThreads?: {
|
|
126
|
+
pageInfo: {
|
|
127
|
+
endCursor: string | null;
|
|
128
|
+
hasNextPage: boolean;
|
|
129
|
+
};
|
|
130
|
+
nodes: Array<{
|
|
131
|
+
isResolved: boolean;
|
|
132
|
+
}>;
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
type SlimPullRequestBatchResponse = {
|
|
137
|
+
data?: Record<
|
|
138
|
+
string,
|
|
139
|
+
{ pullRequest?: SlimPullRequestNodeResponse | null } | null
|
|
140
|
+
> | null;
|
|
141
|
+
errors?: Array<{
|
|
142
|
+
message: string;
|
|
143
|
+
type?: string;
|
|
144
|
+
path?: Array<string | number>;
|
|
145
|
+
}>;
|
|
146
|
+
};
|
|
147
|
+
|
|
111
148
|
type SlimPullRequestResponse = {
|
|
112
149
|
data?: {
|
|
113
150
|
repository?: {
|
|
@@ -206,6 +243,13 @@ function isSlimPullRequestResponse(
|
|
|
206
243
|
return true;
|
|
207
244
|
}
|
|
208
245
|
|
|
246
|
+
function isSlimPullRequestBatchResponse(
|
|
247
|
+
value: unknown,
|
|
248
|
+
): value is SlimPullRequestBatchResponse {
|
|
249
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
209
253
|
function isBranchRulesResponse(
|
|
210
254
|
value: unknown,
|
|
211
255
|
): value is BranchRulesResponseItem[] {
|
|
@@ -423,6 +467,7 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
423
467
|
RestIssueRepository,
|
|
424
468
|
| 'createNewIssue'
|
|
425
469
|
| 'updateIssue'
|
|
470
|
+
| 'updateIssueBody'
|
|
426
471
|
| 'createComment'
|
|
427
472
|
| 'getIssue'
|
|
428
473
|
| 'updateLabels'
|
|
@@ -759,6 +804,12 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
759
804
|
updateIssue = async (issue: Issue): Promise<void> => {
|
|
760
805
|
await this.restIssueRepository.updateIssue(issue);
|
|
761
806
|
};
|
|
807
|
+
updateIssueBody = async (
|
|
808
|
+
issue: Pick<Issue, 'org' | 'repo' | 'number'>,
|
|
809
|
+
body: string,
|
|
810
|
+
): Promise<void> => {
|
|
811
|
+
await this.restIssueRepository.updateIssueBody(issue, body);
|
|
812
|
+
};
|
|
762
813
|
getIssueByUrl = async (url: string): Promise<Issue | null> => {
|
|
763
814
|
const projectItem =
|
|
764
815
|
await this.graphqlProjectItemRepository.fetchProjectItemByUrl(url);
|
|
@@ -1691,6 +1742,214 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1691
1742
|
return this.buildRelatedPullRequestFromSlim(owner, repo, slimPullRequest);
|
|
1692
1743
|
};
|
|
1693
1744
|
|
|
1745
|
+
// Resolves many pull requests with one GraphQL query per hundred instead of
|
|
1746
|
+
// one query per pull request. A URL this cannot settle is left out of the
|
|
1747
|
+
// returned map rather than mapped to null, so the caller falls back to
|
|
1748
|
+
// getOpenPullRequest for it and an unknown state is never mistaken for an
|
|
1749
|
+
// absent pull request.
|
|
1750
|
+
getOpenPullRequests = async (
|
|
1751
|
+
prUrls: string[],
|
|
1752
|
+
): Promise<Map<string, RelatedPullRequest | null>> => {
|
|
1753
|
+
const resolved = new Map<string, RelatedPullRequest | null>();
|
|
1754
|
+
const parsedByUrl = new Map<
|
|
1755
|
+
string,
|
|
1756
|
+
{ owner: string; repo: string; prNumber: number }
|
|
1757
|
+
>();
|
|
1758
|
+
for (const prUrl of Array.from(new Set(prUrls))) {
|
|
1759
|
+
const parsedUrl = this.parseIssueUrl(prUrl);
|
|
1760
|
+
if (!parsedUrl.isPr) {
|
|
1761
|
+
resolved.set(prUrl, null);
|
|
1762
|
+
continue;
|
|
1763
|
+
}
|
|
1764
|
+
parsedByUrl.set(prUrl, {
|
|
1765
|
+
owner: parsedUrl.owner,
|
|
1766
|
+
repo: parsedUrl.repo,
|
|
1767
|
+
prNumber: parsedUrl.issueNumber,
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
const urlsToFetch = Array.from(parsedByUrl.keys());
|
|
1772
|
+
for (
|
|
1773
|
+
let start = 0;
|
|
1774
|
+
start < urlsToFetch.length;
|
|
1775
|
+
start += SLIM_PULL_REQUEST_BATCH_SIZE
|
|
1776
|
+
) {
|
|
1777
|
+
const batchUrls = urlsToFetch.slice(
|
|
1778
|
+
start,
|
|
1779
|
+
start + SLIM_PULL_REQUEST_BATCH_SIZE,
|
|
1780
|
+
);
|
|
1781
|
+
let slimByUrl: Map<string, SlimPullRequest | null>;
|
|
1782
|
+
try {
|
|
1783
|
+
slimByUrl = await this.fetchSlimPullRequestsInOneQuery(
|
|
1784
|
+
batchUrls.map((prUrl) => ({
|
|
1785
|
+
prUrl,
|
|
1786
|
+
...this.requireParsedPullRequest(parsedByUrl, prUrl),
|
|
1787
|
+
})),
|
|
1788
|
+
);
|
|
1789
|
+
} catch (error) {
|
|
1790
|
+
console.warn(
|
|
1791
|
+
`ApiV3CheerioRestIssueRepository: batched pull request status query failed, leaving ${batchUrls.length} pull request(s) to per-request resolution. error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1792
|
+
);
|
|
1793
|
+
continue;
|
|
1794
|
+
}
|
|
1795
|
+
for (const prUrl of batchUrls) {
|
|
1796
|
+
if (!slimByUrl.has(prUrl)) {
|
|
1797
|
+
continue;
|
|
1798
|
+
}
|
|
1799
|
+
const slimPullRequest = slimByUrl.get(prUrl) ?? null;
|
|
1800
|
+
if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
|
|
1801
|
+
resolved.set(prUrl, null);
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
const parsed = this.requireParsedPullRequest(parsedByUrl, prUrl);
|
|
1805
|
+
try {
|
|
1806
|
+
resolved.set(
|
|
1807
|
+
prUrl,
|
|
1808
|
+
await this.buildRelatedPullRequestFromSlim(
|
|
1809
|
+
parsed.owner,
|
|
1810
|
+
parsed.repo,
|
|
1811
|
+
slimPullRequest,
|
|
1812
|
+
),
|
|
1813
|
+
);
|
|
1814
|
+
} catch (error) {
|
|
1815
|
+
console.warn(
|
|
1816
|
+
`ApiV3CheerioRestIssueRepository: building the pull request status failed, leaving it to per-request resolution. prUrl: ${prUrl} error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
return resolved;
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1824
|
+
private requireParsedPullRequest = (
|
|
1825
|
+
parsedByUrl: Map<string, { owner: string; repo: string; prNumber: number }>,
|
|
1826
|
+
prUrl: string,
|
|
1827
|
+
): { owner: string; repo: string; prNumber: number } => {
|
|
1828
|
+
const parsed = parsedByUrl.get(prUrl);
|
|
1829
|
+
if (!parsed) {
|
|
1830
|
+
throw new Error(`Pull request URL was not parsed: ${prUrl}`);
|
|
1831
|
+
}
|
|
1832
|
+
return parsed;
|
|
1833
|
+
};
|
|
1834
|
+
|
|
1835
|
+
// A pull request whose review threads do not fit the first page is omitted
|
|
1836
|
+
// from the returned map, so the caller resolves it through the paginating
|
|
1837
|
+
// single-pull-request path and the resolved-thread state stays complete.
|
|
1838
|
+
private fetchSlimPullRequestsInOneQuery = async (
|
|
1839
|
+
references: {
|
|
1840
|
+
prUrl: string;
|
|
1841
|
+
owner: string;
|
|
1842
|
+
repo: string;
|
|
1843
|
+
prNumber: number;
|
|
1844
|
+
}[],
|
|
1845
|
+
): Promise<Map<string, SlimPullRequest | null>> => {
|
|
1846
|
+
const aliasOf = (index: number): string => `pullRequest${index}`;
|
|
1847
|
+
const variableDeclarations = references
|
|
1848
|
+
.map(
|
|
1849
|
+
(_, index) =>
|
|
1850
|
+
`$owner${index}: String!, $repo${index}: String!, $prNumber${index}: Int!`,
|
|
1851
|
+
)
|
|
1852
|
+
.join(', ');
|
|
1853
|
+
const selections = references
|
|
1854
|
+
.map(
|
|
1855
|
+
(
|
|
1856
|
+
_,
|
|
1857
|
+
index,
|
|
1858
|
+
) => ` ${aliasOf(index)}: repository(owner: $owner${index}, name: $repo${index}) {
|
|
1859
|
+
pullRequest(number: $prNumber${index}) {
|
|
1860
|
+
url
|
|
1861
|
+
state
|
|
1862
|
+
isDraft
|
|
1863
|
+
headRefName
|
|
1864
|
+
baseRefName
|
|
1865
|
+
mergeable
|
|
1866
|
+
headRefOid
|
|
1867
|
+
reviewThreads(first: ${SLIM_PULL_REQUEST_REVIEW_THREADS_PAGE_SIZE}) {
|
|
1868
|
+
pageInfo {
|
|
1869
|
+
endCursor
|
|
1870
|
+
hasNextPage
|
|
1871
|
+
}
|
|
1872
|
+
nodes {
|
|
1873
|
+
isResolved
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
}`,
|
|
1878
|
+
)
|
|
1879
|
+
.join('\n');
|
|
1880
|
+
const query = `query PullRequestSlimStatusBatch(${variableDeclarations}) {\n${selections}\n}`;
|
|
1881
|
+
const variables: Record<string, string | number> = {};
|
|
1882
|
+
references.forEach((reference, index) => {
|
|
1883
|
+
variables[`owner${index}`] = reference.owner;
|
|
1884
|
+
variables[`repo${index}`] = reference.repo;
|
|
1885
|
+
variables[`prNumber${index}`] = reference.prNumber;
|
|
1886
|
+
});
|
|
1887
|
+
|
|
1888
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1889
|
+
fetchGithubGraphql({ ghToken: this.ghToken, query, variables }),
|
|
1890
|
+
);
|
|
1891
|
+
if (!response.ok) {
|
|
1892
|
+
throw new Error(
|
|
1893
|
+
`Failed to fetch pull requests from GitHub GraphQL API: HTTP ${response.status}`,
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
const responseData: unknown = await response.json();
|
|
1897
|
+
if (!isSlimPullRequestBatchResponse(responseData)) {
|
|
1898
|
+
throw new Error('Unexpected response shape when fetching pull requests');
|
|
1899
|
+
}
|
|
1900
|
+
const errors = responseData.errors || [];
|
|
1901
|
+
const notFoundAliases = new Set(
|
|
1902
|
+
errors
|
|
1903
|
+
.filter((error) => error.type === 'NOT_FOUND')
|
|
1904
|
+
.map((error) => String(error.path?.[0] ?? '')),
|
|
1905
|
+
);
|
|
1906
|
+
const otherErrorAliases = new Set(
|
|
1907
|
+
errors
|
|
1908
|
+
.filter((error) => error.type !== 'NOT_FOUND')
|
|
1909
|
+
.map((error) => String(error.path?.[0] ?? '')),
|
|
1910
|
+
);
|
|
1911
|
+
if (!responseData.data) {
|
|
1912
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(errors)}`);
|
|
1913
|
+
}
|
|
1914
|
+
const unattributedError = errors.some(
|
|
1915
|
+
(error) => error.type !== 'NOT_FOUND' && !error.path,
|
|
1916
|
+
);
|
|
1917
|
+
if (unattributedError) {
|
|
1918
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(errors)}`);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
const slimByUrl = new Map<string, SlimPullRequest | null>();
|
|
1922
|
+
references.forEach((reference, index) => {
|
|
1923
|
+
const alias = aliasOf(index);
|
|
1924
|
+
if (otherErrorAliases.has(alias)) {
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
const node = responseData.data?.[alias]?.pullRequest ?? null;
|
|
1928
|
+
if (!node) {
|
|
1929
|
+
if (notFoundAliases.has(alias) || !errors.length) {
|
|
1930
|
+
slimByUrl.set(reference.prUrl, null);
|
|
1931
|
+
}
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
if (node.reviewThreads?.pageInfo.hasNextPage === true) {
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
slimByUrl.set(reference.prUrl, {
|
|
1938
|
+
url: node.url,
|
|
1939
|
+
state: node.state,
|
|
1940
|
+
isDraft: node.isDraft,
|
|
1941
|
+
headRefName: node.headRefName,
|
|
1942
|
+
baseRefName: node.baseRefName,
|
|
1943
|
+
mergeable: node.mergeable,
|
|
1944
|
+
headRefOid: node.headRefOid,
|
|
1945
|
+
reviewThreads: (node.reviewThreads?.nodes || []).map((thread) => ({
|
|
1946
|
+
isResolved: thread.isResolved,
|
|
1947
|
+
})),
|
|
1948
|
+
});
|
|
1949
|
+
});
|
|
1950
|
+
return slimByUrl;
|
|
1951
|
+
};
|
|
1952
|
+
|
|
1694
1953
|
closePullRequest = async (prUrl: string): Promise<void> => {
|
|
1695
1954
|
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
1696
1955
|
const response = await this.fetchWithRateLimitRetry(() =>
|
|
@@ -2044,9 +2303,9 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
2044
2303
|
await this.restIssueRepository.createComment(issueOrPrUrl, commentBody);
|
|
2045
2304
|
};
|
|
2046
2305
|
|
|
2047
|
-
|
|
2306
|
+
private fetchIssueBodyResponse = (url: string): Promise<Response> => {
|
|
2048
2307
|
const { owner, repo, issueNumber } = this.parseIssueUrl(url);
|
|
2049
|
-
|
|
2308
|
+
return this.fetchWithRateLimitRetry(() =>
|
|
2050
2309
|
fetch(
|
|
2051
2310
|
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`,
|
|
2052
2311
|
{
|
|
@@ -2058,10 +2317,12 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
2058
2317
|
},
|
|
2059
2318
|
),
|
|
2060
2319
|
);
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2320
|
+
};
|
|
2321
|
+
|
|
2322
|
+
private parseIssueBodyResponse = async (
|
|
2323
|
+
url: string,
|
|
2324
|
+
response: Response,
|
|
2325
|
+
): Promise<string> => {
|
|
2065
2326
|
const body: unknown = await response.json();
|
|
2066
2327
|
if (!isIssueOrPullRequestBodyResponse(body)) {
|
|
2067
2328
|
throw new Error(
|
|
@@ -2071,6 +2332,27 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
2071
2332
|
return body.body ?? '';
|
|
2072
2333
|
};
|
|
2073
2334
|
|
|
2335
|
+
getIssueOrPullRequestBody = async (url: string): Promise<string> => {
|
|
2336
|
+
const response = await this.fetchIssueBodyResponse(url);
|
|
2337
|
+
if (!response.ok) {
|
|
2338
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2339
|
+
throw new Error(`Failed to fetch body for ${url}: ${reason}`);
|
|
2340
|
+
}
|
|
2341
|
+
return this.parseIssueBodyResponse(url, response);
|
|
2342
|
+
};
|
|
2343
|
+
|
|
2344
|
+
getIssueBodyByUrl = async (url: string): Promise<string | null> => {
|
|
2345
|
+
const response = await this.fetchIssueBodyResponse(url);
|
|
2346
|
+
if (response.status === 404) {
|
|
2347
|
+
return null;
|
|
2348
|
+
}
|
|
2349
|
+
if (!response.ok) {
|
|
2350
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2351
|
+
throw new Error(`Failed to fetch body for ${url}: ${reason}`);
|
|
2352
|
+
}
|
|
2353
|
+
return this.parseIssueBodyResponse(url, response);
|
|
2354
|
+
};
|
|
2355
|
+
|
|
2074
2356
|
getIssueOrPullRequestComments = async (
|
|
2075
2357
|
url: string,
|
|
2076
2358
|
): Promise<IssueComment[]> => {
|
|
@@ -263,6 +263,25 @@ describe('RestIssueRepository', () => {
|
|
|
263
263
|
});
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
+
describe('updateIssueBody', () => {
|
|
267
|
+
it('sends only the body so the title, labels, assignees and state are left untouched', async () => {
|
|
268
|
+
const issue = buildIssue();
|
|
269
|
+
|
|
270
|
+
mockPatch.mockResolvedValue(undefined);
|
|
271
|
+
|
|
272
|
+
await restIssueRepository.updateIssueBody(issue, 'rewritten body');
|
|
273
|
+
|
|
274
|
+
expect(mockPatch).toHaveBeenCalledTimes(1);
|
|
275
|
+
expect(mockPatch).toHaveBeenCalledWith(
|
|
276
|
+
'https://api.github.com/repos/HiromiShikata/test-repository/issues/40',
|
|
277
|
+
{
|
|
278
|
+
json: { body: 'rewritten body' },
|
|
279
|
+
headers: { Authorization: 'token dummy-token' },
|
|
280
|
+
},
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
266
285
|
describe('searchIssues', () => {
|
|
267
286
|
const buildSearchItem = (
|
|
268
287
|
overrides: Partial<{
|
|
@@ -105,6 +105,19 @@ export class RestIssueRepository
|
|
|
105
105
|
);
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
+
updateIssueBody = async (
|
|
109
|
+
issue: Pick<Issue, 'org' | 'repo' | 'number'>,
|
|
110
|
+
body: string,
|
|
111
|
+
): Promise<void> => {
|
|
112
|
+
await ky.patch(
|
|
113
|
+
`https://api.github.com/repos/${issue.org}/${issue.repo}/issues/${issue.number}`,
|
|
114
|
+
{
|
|
115
|
+
json: { body },
|
|
116
|
+
headers: { Authorization: `token ${this.ghToken}` },
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
108
121
|
updateLabels = async (
|
|
109
122
|
issue: Issue,
|
|
110
123
|
labels: Issue['labels'],
|