@harness-engineering/core 0.17.0 → 0.18.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/dist/architecture/matchers.d.mts +1 -1
- package/dist/architecture/matchers.d.ts +1 -1
- package/dist/index.d.mts +104 -84
- package/dist/index.d.ts +104 -84
- package/dist/index.js +118 -28
- package/dist/index.mjs +118 -28
- package/dist/{matchers-D20x48U9.d.mts → matchers-Dj1t5vpg.d.mts} +46 -46
- package/dist/{matchers-D20x48U9.d.ts → matchers-Dj1t5vpg.d.ts} +46 -46
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12574,6 +12574,29 @@ function labelsForStatus(status, config) {
|
|
|
12574
12574
|
}
|
|
12575
12575
|
return [...base];
|
|
12576
12576
|
}
|
|
12577
|
+
var RETRY_DEFAULTS = { maxRetries: 5, baseDelayMs: 1e3 };
|
|
12578
|
+
function sleep(ms) {
|
|
12579
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
12580
|
+
}
|
|
12581
|
+
async function fetchWithRetry(fetchFn, input, init, opts = RETRY_DEFAULTS) {
|
|
12582
|
+
let lastResponse;
|
|
12583
|
+
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
12584
|
+
const response = await fetchFn(input, init);
|
|
12585
|
+
if (response.status !== 403 && response.status !== 429) return response;
|
|
12586
|
+
lastResponse = response;
|
|
12587
|
+
if (attempt === opts.maxRetries) break;
|
|
12588
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
12589
|
+
let delayMs;
|
|
12590
|
+
if (retryAfter) {
|
|
12591
|
+
const seconds = parseInt(retryAfter, 10);
|
|
12592
|
+
delayMs = isNaN(seconds) ? opts.baseDelayMs : seconds * 1e3;
|
|
12593
|
+
} else {
|
|
12594
|
+
delayMs = opts.baseDelayMs * Math.pow(2, attempt) + Math.random() * 500;
|
|
12595
|
+
}
|
|
12596
|
+
await sleep(delayMs);
|
|
12597
|
+
}
|
|
12598
|
+
return lastResponse;
|
|
12599
|
+
}
|
|
12577
12600
|
var GitHubIssuesSyncAdapter = class {
|
|
12578
12601
|
token;
|
|
12579
12602
|
config;
|
|
@@ -12581,11 +12604,18 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12581
12604
|
apiBase;
|
|
12582
12605
|
owner;
|
|
12583
12606
|
repo;
|
|
12607
|
+
retryOpts;
|
|
12608
|
+
/** Cached GitHub milestone name -> ID mapping */
|
|
12609
|
+
milestoneCache = null;
|
|
12584
12610
|
constructor(options) {
|
|
12585
12611
|
this.token = options.token;
|
|
12586
12612
|
this.config = options.config;
|
|
12587
12613
|
this.fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
12588
12614
|
this.apiBase = options.apiBase ?? "https://api.github.com";
|
|
12615
|
+
this.retryOpts = {
|
|
12616
|
+
maxRetries: options.maxRetries ?? RETRY_DEFAULTS.maxRetries,
|
|
12617
|
+
baseDelayMs: options.baseDelayMs ?? RETRY_DEFAULTS.baseDelayMs
|
|
12618
|
+
};
|
|
12589
12619
|
const repoParts = (options.config.repo ?? "").split("/");
|
|
12590
12620
|
if (repoParts.length !== 2 || !repoParts[0] || !repoParts[1]) {
|
|
12591
12621
|
throw new Error(`Invalid repo format: "${options.config.repo}". Expected "owner/repo".`);
|
|
@@ -12593,6 +12623,43 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12593
12623
|
this.owner = repoParts[0];
|
|
12594
12624
|
this.repo = repoParts[1];
|
|
12595
12625
|
}
|
|
12626
|
+
/**
|
|
12627
|
+
* Fetch all GitHub milestones and build the name -> ID cache.
|
|
12628
|
+
*/
|
|
12629
|
+
async loadMilestones() {
|
|
12630
|
+
if (this.milestoneCache) return this.milestoneCache;
|
|
12631
|
+
this.milestoneCache = /* @__PURE__ */ new Map();
|
|
12632
|
+
const response = await fetchWithRetry(
|
|
12633
|
+
this.fetchFn,
|
|
12634
|
+
`${this.apiBase}/repos/${this.owner}/${this.repo}/milestones?state=all&per_page=100`,
|
|
12635
|
+
{ method: "GET", headers: this.headers() },
|
|
12636
|
+
this.retryOpts
|
|
12637
|
+
);
|
|
12638
|
+
if (response.ok) {
|
|
12639
|
+
const data = await response.json();
|
|
12640
|
+
for (const m of data) {
|
|
12641
|
+
this.milestoneCache.set(m.title, m.number);
|
|
12642
|
+
}
|
|
12643
|
+
}
|
|
12644
|
+
return this.milestoneCache;
|
|
12645
|
+
}
|
|
12646
|
+
/**
|
|
12647
|
+
* Get or create a GitHub milestone by name. Returns the milestone number.
|
|
12648
|
+
*/
|
|
12649
|
+
async ensureMilestone(name) {
|
|
12650
|
+
const cache = await this.loadMilestones();
|
|
12651
|
+
if (cache.has(name)) return cache.get(name);
|
|
12652
|
+
const response = await fetchWithRetry(
|
|
12653
|
+
this.fetchFn,
|
|
12654
|
+
`${this.apiBase}/repos/${this.owner}/${this.repo}/milestones`,
|
|
12655
|
+
{ method: "POST", headers: this.headers(), body: JSON.stringify({ title: name }) },
|
|
12656
|
+
this.retryOpts
|
|
12657
|
+
);
|
|
12658
|
+
if (!response.ok) return null;
|
|
12659
|
+
const data = await response.json();
|
|
12660
|
+
cache.set(name, data.number);
|
|
12661
|
+
return data.number;
|
|
12662
|
+
}
|
|
12596
12663
|
headers() {
|
|
12597
12664
|
return {
|
|
12598
12665
|
Authorization: `Bearer ${this.token}`,
|
|
@@ -12601,26 +12668,32 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12601
12668
|
"X-GitHub-Api-Version": "2022-11-28"
|
|
12602
12669
|
};
|
|
12603
12670
|
}
|
|
12671
|
+
/**
|
|
12672
|
+
* Close an issue if the feature status maps to 'closed'.
|
|
12673
|
+
* GitHub Issues API doesn't accept state on POST — requires a follow-up PATCH.
|
|
12674
|
+
*/
|
|
12675
|
+
async closeIfDone(issueNumber, featureStatus) {
|
|
12676
|
+
const externalStatus = this.config.statusMap[featureStatus];
|
|
12677
|
+
if (externalStatus !== "closed") return;
|
|
12678
|
+
await fetchWithRetry(
|
|
12679
|
+
this.fetchFn,
|
|
12680
|
+
`${this.apiBase}/repos/${this.owner}/${this.repo}/issues/${issueNumber}`,
|
|
12681
|
+
{ method: "PATCH", headers: this.headers(), body: JSON.stringify({ state: "closed" }) },
|
|
12682
|
+
this.retryOpts
|
|
12683
|
+
);
|
|
12684
|
+
}
|
|
12604
12685
|
async createTicket(feature, milestone) {
|
|
12605
12686
|
try {
|
|
12606
|
-
const labels = labelsForStatus(feature.status, this.config);
|
|
12607
|
-
const body = [
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
const response = await this.fetchFn(
|
|
12687
|
+
const labels = [...labelsForStatus(feature.status, this.config), "feature"];
|
|
12688
|
+
const body = [feature.summary, "", feature.spec ? `**Spec:** ${feature.spec}` : ""].filter(Boolean).join("\n");
|
|
12689
|
+
const milestoneId = await this.ensureMilestone(milestone);
|
|
12690
|
+
const issuePayload = { title: feature.name, body, labels };
|
|
12691
|
+
if (milestoneId) issuePayload.milestone = milestoneId;
|
|
12692
|
+
const response = await fetchWithRetry(
|
|
12693
|
+
this.fetchFn,
|
|
12614
12694
|
`${this.apiBase}/repos/${this.owner}/${this.repo}/issues`,
|
|
12615
|
-
{
|
|
12616
|
-
|
|
12617
|
-
headers: this.headers(),
|
|
12618
|
-
body: JSON.stringify({
|
|
12619
|
-
title: feature.name,
|
|
12620
|
-
body,
|
|
12621
|
-
labels
|
|
12622
|
-
})
|
|
12623
|
-
}
|
|
12695
|
+
{ method: "POST", headers: this.headers(), body: JSON.stringify(issuePayload) },
|
|
12696
|
+
this.retryOpts
|
|
12624
12697
|
);
|
|
12625
12698
|
if (!response.ok) {
|
|
12626
12699
|
const text = await response.text();
|
|
@@ -12628,12 +12701,13 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12628
12701
|
}
|
|
12629
12702
|
const data = await response.json();
|
|
12630
12703
|
const externalId = buildExternalId(this.owner, this.repo, data.number);
|
|
12704
|
+
await this.closeIfDone(data.number, feature.status);
|
|
12631
12705
|
return (0, import_types21.Ok)({ externalId, url: data.html_url });
|
|
12632
12706
|
} catch (error) {
|
|
12633
12707
|
return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
12634
12708
|
}
|
|
12635
12709
|
}
|
|
12636
|
-
async updateTicket(externalId, changes) {
|
|
12710
|
+
async updateTicket(externalId, changes, milestone) {
|
|
12637
12711
|
try {
|
|
12638
12712
|
const parsed = parseExternalId(externalId);
|
|
12639
12713
|
if (!parsed) return (0, import_types21.Err)(new Error(`Invalid externalId format: "${externalId}"`));
|
|
@@ -12646,15 +12720,21 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12646
12720
|
if (changes.status !== void 0) {
|
|
12647
12721
|
const externalStatus = this.config.statusMap[changes.status];
|
|
12648
12722
|
patch.state = externalStatus;
|
|
12649
|
-
patch.labels = labelsForStatus(changes.status, this.config);
|
|
12723
|
+
patch.labels = [...labelsForStatus(changes.status, this.config), "feature"];
|
|
12724
|
+
}
|
|
12725
|
+
if (milestone) {
|
|
12726
|
+
const milestoneId = await this.ensureMilestone(milestone);
|
|
12727
|
+
if (milestoneId) patch.milestone = milestoneId;
|
|
12650
12728
|
}
|
|
12651
|
-
const response = await
|
|
12729
|
+
const response = await fetchWithRetry(
|
|
12730
|
+
this.fetchFn,
|
|
12652
12731
|
`${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
|
|
12653
12732
|
{
|
|
12654
12733
|
method: "PATCH",
|
|
12655
12734
|
headers: this.headers(),
|
|
12656
12735
|
body: JSON.stringify(patch)
|
|
12657
|
-
}
|
|
12736
|
+
},
|
|
12737
|
+
this.retryOpts
|
|
12658
12738
|
);
|
|
12659
12739
|
if (!response.ok) {
|
|
12660
12740
|
const text = await response.text();
|
|
@@ -12670,12 +12750,14 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12670
12750
|
try {
|
|
12671
12751
|
const parsed = parseExternalId(externalId);
|
|
12672
12752
|
if (!parsed) return (0, import_types21.Err)(new Error(`Invalid externalId format: "${externalId}"`));
|
|
12673
|
-
const response = await
|
|
12753
|
+
const response = await fetchWithRetry(
|
|
12754
|
+
this.fetchFn,
|
|
12674
12755
|
`${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
|
|
12675
12756
|
{
|
|
12676
12757
|
method: "GET",
|
|
12677
12758
|
headers: this.headers()
|
|
12678
|
-
}
|
|
12759
|
+
},
|
|
12760
|
+
this.retryOpts
|
|
12679
12761
|
);
|
|
12680
12762
|
if (!response.ok) {
|
|
12681
12763
|
const text = await response.text();
|
|
@@ -12700,12 +12782,14 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12700
12782
|
let page = 1;
|
|
12701
12783
|
const perPage = 100;
|
|
12702
12784
|
while (true) {
|
|
12703
|
-
const response = await
|
|
12785
|
+
const response = await fetchWithRetry(
|
|
12786
|
+
this.fetchFn,
|
|
12704
12787
|
`${this.apiBase}/repos/${this.owner}/${this.repo}/issues?state=all&per_page=${perPage}&page=${page}${labelsParam}`,
|
|
12705
12788
|
{
|
|
12706
12789
|
method: "GET",
|
|
12707
12790
|
headers: this.headers()
|
|
12708
|
-
}
|
|
12791
|
+
},
|
|
12792
|
+
this.retryOpts
|
|
12709
12793
|
);
|
|
12710
12794
|
if (!response.ok) {
|
|
12711
12795
|
const text = await response.text();
|
|
@@ -12734,13 +12818,15 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12734
12818
|
const parsed = parseExternalId(externalId);
|
|
12735
12819
|
if (!parsed) return (0, import_types21.Err)(new Error(`Invalid externalId format: "${externalId}"`));
|
|
12736
12820
|
const login = assignee.startsWith("@") ? assignee.slice(1) : assignee;
|
|
12737
|
-
const response = await
|
|
12821
|
+
const response = await fetchWithRetry(
|
|
12822
|
+
this.fetchFn,
|
|
12738
12823
|
`${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/assignees`,
|
|
12739
12824
|
{
|
|
12740
12825
|
method: "POST",
|
|
12741
12826
|
headers: this.headers(),
|
|
12742
12827
|
body: JSON.stringify({ assignees: [login] })
|
|
12743
|
-
}
|
|
12828
|
+
},
|
|
12829
|
+
this.retryOpts
|
|
12744
12830
|
);
|
|
12745
12831
|
if (!response.ok) {
|
|
12746
12832
|
const text = await response.text();
|
|
@@ -12771,7 +12857,11 @@ async function syncToExternal(roadmap, adapter, _config) {
|
|
|
12771
12857
|
result.errors.push({ featureOrId: feature.name, error: createResult.error });
|
|
12772
12858
|
}
|
|
12773
12859
|
} else {
|
|
12774
|
-
const updateResult = await adapter.updateTicket(
|
|
12860
|
+
const updateResult = await adapter.updateTicket(
|
|
12861
|
+
feature.externalId,
|
|
12862
|
+
feature,
|
|
12863
|
+
milestone.name
|
|
12864
|
+
);
|
|
12775
12865
|
if (updateResult.ok) {
|
|
12776
12866
|
result.updated.push(feature.externalId);
|
|
12777
12867
|
} else {
|
package/dist/index.mjs
CHANGED
|
@@ -10465,6 +10465,29 @@ function labelsForStatus(status, config) {
|
|
|
10465
10465
|
}
|
|
10466
10466
|
return [...base];
|
|
10467
10467
|
}
|
|
10468
|
+
var RETRY_DEFAULTS = { maxRetries: 5, baseDelayMs: 1e3 };
|
|
10469
|
+
function sleep(ms) {
|
|
10470
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
10471
|
+
}
|
|
10472
|
+
async function fetchWithRetry(fetchFn, input, init, opts = RETRY_DEFAULTS) {
|
|
10473
|
+
let lastResponse;
|
|
10474
|
+
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
10475
|
+
const response = await fetchFn(input, init);
|
|
10476
|
+
if (response.status !== 403 && response.status !== 429) return response;
|
|
10477
|
+
lastResponse = response;
|
|
10478
|
+
if (attempt === opts.maxRetries) break;
|
|
10479
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
10480
|
+
let delayMs;
|
|
10481
|
+
if (retryAfter) {
|
|
10482
|
+
const seconds = parseInt(retryAfter, 10);
|
|
10483
|
+
delayMs = isNaN(seconds) ? opts.baseDelayMs : seconds * 1e3;
|
|
10484
|
+
} else {
|
|
10485
|
+
delayMs = opts.baseDelayMs * Math.pow(2, attempt) + Math.random() * 500;
|
|
10486
|
+
}
|
|
10487
|
+
await sleep(delayMs);
|
|
10488
|
+
}
|
|
10489
|
+
return lastResponse;
|
|
10490
|
+
}
|
|
10468
10491
|
var GitHubIssuesSyncAdapter = class {
|
|
10469
10492
|
token;
|
|
10470
10493
|
config;
|
|
@@ -10472,11 +10495,18 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
10472
10495
|
apiBase;
|
|
10473
10496
|
owner;
|
|
10474
10497
|
repo;
|
|
10498
|
+
retryOpts;
|
|
10499
|
+
/** Cached GitHub milestone name -> ID mapping */
|
|
10500
|
+
milestoneCache = null;
|
|
10475
10501
|
constructor(options) {
|
|
10476
10502
|
this.token = options.token;
|
|
10477
10503
|
this.config = options.config;
|
|
10478
10504
|
this.fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
10479
10505
|
this.apiBase = options.apiBase ?? "https://api.github.com";
|
|
10506
|
+
this.retryOpts = {
|
|
10507
|
+
maxRetries: options.maxRetries ?? RETRY_DEFAULTS.maxRetries,
|
|
10508
|
+
baseDelayMs: options.baseDelayMs ?? RETRY_DEFAULTS.baseDelayMs
|
|
10509
|
+
};
|
|
10480
10510
|
const repoParts = (options.config.repo ?? "").split("/");
|
|
10481
10511
|
if (repoParts.length !== 2 || !repoParts[0] || !repoParts[1]) {
|
|
10482
10512
|
throw new Error(`Invalid repo format: "${options.config.repo}". Expected "owner/repo".`);
|
|
@@ -10484,6 +10514,43 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
10484
10514
|
this.owner = repoParts[0];
|
|
10485
10515
|
this.repo = repoParts[1];
|
|
10486
10516
|
}
|
|
10517
|
+
/**
|
|
10518
|
+
* Fetch all GitHub milestones and build the name -> ID cache.
|
|
10519
|
+
*/
|
|
10520
|
+
async loadMilestones() {
|
|
10521
|
+
if (this.milestoneCache) return this.milestoneCache;
|
|
10522
|
+
this.milestoneCache = /* @__PURE__ */ new Map();
|
|
10523
|
+
const response = await fetchWithRetry(
|
|
10524
|
+
this.fetchFn,
|
|
10525
|
+
`${this.apiBase}/repos/${this.owner}/${this.repo}/milestones?state=all&per_page=100`,
|
|
10526
|
+
{ method: "GET", headers: this.headers() },
|
|
10527
|
+
this.retryOpts
|
|
10528
|
+
);
|
|
10529
|
+
if (response.ok) {
|
|
10530
|
+
const data = await response.json();
|
|
10531
|
+
for (const m of data) {
|
|
10532
|
+
this.milestoneCache.set(m.title, m.number);
|
|
10533
|
+
}
|
|
10534
|
+
}
|
|
10535
|
+
return this.milestoneCache;
|
|
10536
|
+
}
|
|
10537
|
+
/**
|
|
10538
|
+
* Get or create a GitHub milestone by name. Returns the milestone number.
|
|
10539
|
+
*/
|
|
10540
|
+
async ensureMilestone(name) {
|
|
10541
|
+
const cache = await this.loadMilestones();
|
|
10542
|
+
if (cache.has(name)) return cache.get(name);
|
|
10543
|
+
const response = await fetchWithRetry(
|
|
10544
|
+
this.fetchFn,
|
|
10545
|
+
`${this.apiBase}/repos/${this.owner}/${this.repo}/milestones`,
|
|
10546
|
+
{ method: "POST", headers: this.headers(), body: JSON.stringify({ title: name }) },
|
|
10547
|
+
this.retryOpts
|
|
10548
|
+
);
|
|
10549
|
+
if (!response.ok) return null;
|
|
10550
|
+
const data = await response.json();
|
|
10551
|
+
cache.set(name, data.number);
|
|
10552
|
+
return data.number;
|
|
10553
|
+
}
|
|
10487
10554
|
headers() {
|
|
10488
10555
|
return {
|
|
10489
10556
|
Authorization: `Bearer ${this.token}`,
|
|
@@ -10492,26 +10559,32 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
10492
10559
|
"X-GitHub-Api-Version": "2022-11-28"
|
|
10493
10560
|
};
|
|
10494
10561
|
}
|
|
10562
|
+
/**
|
|
10563
|
+
* Close an issue if the feature status maps to 'closed'.
|
|
10564
|
+
* GitHub Issues API doesn't accept state on POST — requires a follow-up PATCH.
|
|
10565
|
+
*/
|
|
10566
|
+
async closeIfDone(issueNumber, featureStatus) {
|
|
10567
|
+
const externalStatus = this.config.statusMap[featureStatus];
|
|
10568
|
+
if (externalStatus !== "closed") return;
|
|
10569
|
+
await fetchWithRetry(
|
|
10570
|
+
this.fetchFn,
|
|
10571
|
+
`${this.apiBase}/repos/${this.owner}/${this.repo}/issues/${issueNumber}`,
|
|
10572
|
+
{ method: "PATCH", headers: this.headers(), body: JSON.stringify({ state: "closed" }) },
|
|
10573
|
+
this.retryOpts
|
|
10574
|
+
);
|
|
10575
|
+
}
|
|
10495
10576
|
async createTicket(feature, milestone) {
|
|
10496
10577
|
try {
|
|
10497
|
-
const labels = labelsForStatus(feature.status, this.config);
|
|
10498
|
-
const body = [
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
const response = await this.fetchFn(
|
|
10578
|
+
const labels = [...labelsForStatus(feature.status, this.config), "feature"];
|
|
10579
|
+
const body = [feature.summary, "", feature.spec ? `**Spec:** ${feature.spec}` : ""].filter(Boolean).join("\n");
|
|
10580
|
+
const milestoneId = await this.ensureMilestone(milestone);
|
|
10581
|
+
const issuePayload = { title: feature.name, body, labels };
|
|
10582
|
+
if (milestoneId) issuePayload.milestone = milestoneId;
|
|
10583
|
+
const response = await fetchWithRetry(
|
|
10584
|
+
this.fetchFn,
|
|
10505
10585
|
`${this.apiBase}/repos/${this.owner}/${this.repo}/issues`,
|
|
10506
|
-
{
|
|
10507
|
-
|
|
10508
|
-
headers: this.headers(),
|
|
10509
|
-
body: JSON.stringify({
|
|
10510
|
-
title: feature.name,
|
|
10511
|
-
body,
|
|
10512
|
-
labels
|
|
10513
|
-
})
|
|
10514
|
-
}
|
|
10586
|
+
{ method: "POST", headers: this.headers(), body: JSON.stringify(issuePayload) },
|
|
10587
|
+
this.retryOpts
|
|
10515
10588
|
);
|
|
10516
10589
|
if (!response.ok) {
|
|
10517
10590
|
const text = await response.text();
|
|
@@ -10519,12 +10592,13 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
10519
10592
|
}
|
|
10520
10593
|
const data = await response.json();
|
|
10521
10594
|
const externalId = buildExternalId(this.owner, this.repo, data.number);
|
|
10595
|
+
await this.closeIfDone(data.number, feature.status);
|
|
10522
10596
|
return Ok4({ externalId, url: data.html_url });
|
|
10523
10597
|
} catch (error) {
|
|
10524
10598
|
return Err3(error instanceof Error ? error : new Error(String(error)));
|
|
10525
10599
|
}
|
|
10526
10600
|
}
|
|
10527
|
-
async updateTicket(externalId, changes) {
|
|
10601
|
+
async updateTicket(externalId, changes, milestone) {
|
|
10528
10602
|
try {
|
|
10529
10603
|
const parsed = parseExternalId(externalId);
|
|
10530
10604
|
if (!parsed) return Err3(new Error(`Invalid externalId format: "${externalId}"`));
|
|
@@ -10537,15 +10611,21 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
10537
10611
|
if (changes.status !== void 0) {
|
|
10538
10612
|
const externalStatus = this.config.statusMap[changes.status];
|
|
10539
10613
|
patch.state = externalStatus;
|
|
10540
|
-
patch.labels = labelsForStatus(changes.status, this.config);
|
|
10614
|
+
patch.labels = [...labelsForStatus(changes.status, this.config), "feature"];
|
|
10615
|
+
}
|
|
10616
|
+
if (milestone) {
|
|
10617
|
+
const milestoneId = await this.ensureMilestone(milestone);
|
|
10618
|
+
if (milestoneId) patch.milestone = milestoneId;
|
|
10541
10619
|
}
|
|
10542
|
-
const response = await
|
|
10620
|
+
const response = await fetchWithRetry(
|
|
10621
|
+
this.fetchFn,
|
|
10543
10622
|
`${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
|
|
10544
10623
|
{
|
|
10545
10624
|
method: "PATCH",
|
|
10546
10625
|
headers: this.headers(),
|
|
10547
10626
|
body: JSON.stringify(patch)
|
|
10548
|
-
}
|
|
10627
|
+
},
|
|
10628
|
+
this.retryOpts
|
|
10549
10629
|
);
|
|
10550
10630
|
if (!response.ok) {
|
|
10551
10631
|
const text = await response.text();
|
|
@@ -10561,12 +10641,14 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
10561
10641
|
try {
|
|
10562
10642
|
const parsed = parseExternalId(externalId);
|
|
10563
10643
|
if (!parsed) return Err3(new Error(`Invalid externalId format: "${externalId}"`));
|
|
10564
|
-
const response = await
|
|
10644
|
+
const response = await fetchWithRetry(
|
|
10645
|
+
this.fetchFn,
|
|
10565
10646
|
`${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
|
|
10566
10647
|
{
|
|
10567
10648
|
method: "GET",
|
|
10568
10649
|
headers: this.headers()
|
|
10569
|
-
}
|
|
10650
|
+
},
|
|
10651
|
+
this.retryOpts
|
|
10570
10652
|
);
|
|
10571
10653
|
if (!response.ok) {
|
|
10572
10654
|
const text = await response.text();
|
|
@@ -10591,12 +10673,14 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
10591
10673
|
let page = 1;
|
|
10592
10674
|
const perPage = 100;
|
|
10593
10675
|
while (true) {
|
|
10594
|
-
const response = await
|
|
10676
|
+
const response = await fetchWithRetry(
|
|
10677
|
+
this.fetchFn,
|
|
10595
10678
|
`${this.apiBase}/repos/${this.owner}/${this.repo}/issues?state=all&per_page=${perPage}&page=${page}${labelsParam}`,
|
|
10596
10679
|
{
|
|
10597
10680
|
method: "GET",
|
|
10598
10681
|
headers: this.headers()
|
|
10599
|
-
}
|
|
10682
|
+
},
|
|
10683
|
+
this.retryOpts
|
|
10600
10684
|
);
|
|
10601
10685
|
if (!response.ok) {
|
|
10602
10686
|
const text = await response.text();
|
|
@@ -10625,13 +10709,15 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
10625
10709
|
const parsed = parseExternalId(externalId);
|
|
10626
10710
|
if (!parsed) return Err3(new Error(`Invalid externalId format: "${externalId}"`));
|
|
10627
10711
|
const login = assignee.startsWith("@") ? assignee.slice(1) : assignee;
|
|
10628
|
-
const response = await
|
|
10712
|
+
const response = await fetchWithRetry(
|
|
10713
|
+
this.fetchFn,
|
|
10629
10714
|
`${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/assignees`,
|
|
10630
10715
|
{
|
|
10631
10716
|
method: "POST",
|
|
10632
10717
|
headers: this.headers(),
|
|
10633
10718
|
body: JSON.stringify({ assignees: [login] })
|
|
10634
|
-
}
|
|
10719
|
+
},
|
|
10720
|
+
this.retryOpts
|
|
10635
10721
|
);
|
|
10636
10722
|
if (!response.ok) {
|
|
10637
10723
|
const text = await response.text();
|
|
@@ -10662,7 +10748,11 @@ async function syncToExternal(roadmap, adapter, _config) {
|
|
|
10662
10748
|
result.errors.push({ featureOrId: feature.name, error: createResult.error });
|
|
10663
10749
|
}
|
|
10664
10750
|
} else {
|
|
10665
|
-
const updateResult = await adapter.updateTicket(
|
|
10751
|
+
const updateResult = await adapter.updateTicket(
|
|
10752
|
+
feature.externalId,
|
|
10753
|
+
feature,
|
|
10754
|
+
milestone.name
|
|
10755
|
+
);
|
|
10666
10756
|
if (updateResult.ok) {
|
|
10667
10757
|
result.updated.push(feature.externalId);
|
|
10668
10758
|
} else {
|