opencode-forgekeeper 2.0.0 → 2.1.1
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 +10 -3
- package/dist/index.js +252 -217
- package/dist/tui.js +444 -101
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -185,10 +185,14 @@ class ForgeError extends Error {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
// packages/forgekeeper/src/graphql.ts
|
|
189
|
+
var query = (text) => text;
|
|
190
|
+
var typed = (transport) => async (host, query2, variables) => await transport(host, query2, variables);
|
|
191
|
+
|
|
188
192
|
// packages/forgekeeper/src/gitlab-feedback.ts
|
|
189
193
|
var PAGE_SIZE = 100;
|
|
190
194
|
var MAX_COMMENT_PAGES = 20;
|
|
191
|
-
var COMMENTS_QUERY = `query($project: ID!, $iid: String!, $before: String) {
|
|
195
|
+
var COMMENTS_QUERY = query(`query($project: ID!, $iid: String!, $before: String) {
|
|
192
196
|
currentUser { id }
|
|
193
197
|
project(fullPath: $project) {
|
|
194
198
|
mergeRequest(iid: $iid) {
|
|
@@ -202,7 +206,7 @@ var COMMENTS_QUERY = `query($project: ID!, $iid: String!, $before: String) {
|
|
|
202
206
|
}
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
|
-
}
|
|
209
|
+
}`);
|
|
206
210
|
var noteNumber = (id) => id.slice(id.lastIndexOf("/") + 1);
|
|
207
211
|
var timestamp = (node) => Date.parse(node.lastEditedAt ?? node.updatedAt ?? node.createdAt);
|
|
208
212
|
function toComment(node) {
|
|
@@ -221,7 +225,8 @@ function toComment(node) {
|
|
|
221
225
|
username: node.author.username
|
|
222
226
|
};
|
|
223
227
|
}
|
|
224
|
-
async function fetchComments(
|
|
228
|
+
async function fetchComments(transport, host, project, iid) {
|
|
229
|
+
const graphql = typed(transport);
|
|
225
230
|
const pages = [];
|
|
226
231
|
let viewerID;
|
|
227
232
|
let before;
|
|
@@ -235,8 +240,8 @@ async function fetchComments(graphql, host, project, iid) {
|
|
|
235
240
|
if (before)
|
|
236
241
|
variables.before = before;
|
|
237
242
|
const data = await graphql(host, COMMENTS_QUERY, variables);
|
|
238
|
-
viewerID ??= data
|
|
239
|
-
const connection = data
|
|
243
|
+
viewerID ??= data.currentUser?.id ?? undefined;
|
|
244
|
+
const connection = data.project?.mergeRequest?.notes;
|
|
240
245
|
if (!connection)
|
|
241
246
|
return page === 0 ? undefined : snapshot(false);
|
|
242
247
|
pages.push(connection.nodes.flatMap((node) => toComment(node) ?? []));
|
|
@@ -268,17 +273,17 @@ function mergeRequestsQuery(projectCount) {
|
|
|
268
273
|
mergeRequests(sourceBranches: [$branch], state: opened, first: 20) { nodes { ${MERGE_REQUEST_FIELDS} } }
|
|
269
274
|
}`).join(`
|
|
270
275
|
`);
|
|
271
|
-
return `query($branch: String!, ${variables}) {
|
|
276
|
+
return query(`query($branch: String!, ${variables}) {
|
|
272
277
|
${projects}
|
|
273
|
-
}
|
|
278
|
+
}`);
|
|
274
279
|
}
|
|
275
280
|
function mergeRequestQuery(projectCount) {
|
|
276
281
|
const variables = Array.from({ length: projectCount }, (_, index) => `$p${index}: ID!`).join(", ");
|
|
277
282
|
const projects = Array.from({ length: projectCount }, (_, index) => `p${index}: project(fullPath: $p${index}) { mergeRequest(iid: $iid) { ${MERGE_REQUEST_FIELDS} } }`).join(`
|
|
278
283
|
`);
|
|
279
|
-
return `query($iid: String!, ${variables}) {
|
|
284
|
+
return query(`query($iid: String!, ${variables}) {
|
|
280
285
|
${projects}
|
|
281
|
-
}
|
|
286
|
+
}`);
|
|
282
287
|
}
|
|
283
288
|
function mergeRequestNumbersQuery(projectCount) {
|
|
284
289
|
const variables = Array.from({ length: projectCount }, (_, index) => `$p${index}: ID!`).join(", ");
|
|
@@ -286,18 +291,18 @@ function mergeRequestNumbersQuery(projectCount) {
|
|
|
286
291
|
mergeRequests(sourceBranches: [$branch], state: opened, first: 20) { nodes { iid updatedAt sourceProject { fullPath } } }
|
|
287
292
|
}`).join(`
|
|
288
293
|
`);
|
|
289
|
-
return `query($branch: String!, ${variables}) {
|
|
294
|
+
return query(`query($branch: String!, ${variables}) {
|
|
290
295
|
${projects}
|
|
291
|
-
}
|
|
296
|
+
}`);
|
|
292
297
|
}
|
|
293
|
-
var SOURCE_BRANCH_QUERY = `query($project: ID!, $iid: String!) {
|
|
298
|
+
var SOURCE_BRANCH_QUERY = query(`query($project: ID!, $iid: String!) {
|
|
294
299
|
project(fullPath: $project) { mergeRequest(iid: $iid) { sourceBranch } }
|
|
295
|
-
}
|
|
296
|
-
var DISCUSSIONS_QUERY = `query($project: ID!, $iid: String!, $after: String) {
|
|
300
|
+
}`);
|
|
301
|
+
var DISCUSSIONS_QUERY = query(`query($project: ID!, $iid: String!, $after: String) {
|
|
297
302
|
project(fullPath: $project) {
|
|
298
303
|
mergeRequest(iid: $iid) { discussions(first: ${PAGE_SIZE2}, after: $after) { ${DISCUSSIONS} } }
|
|
299
304
|
}
|
|
300
|
-
}
|
|
305
|
+
}`);
|
|
301
306
|
function classifyError(message, code) {
|
|
302
307
|
const text = message.trim().replace(/^glab:\s*/, "") || `glab exited with code ${code}`;
|
|
303
308
|
if (/ENOENT|command not found/i.test(text))
|
|
@@ -312,8 +317,8 @@ function classifyError(message, code) {
|
|
|
312
317
|
}
|
|
313
318
|
var isResponse = (body) => typeof body === "object" && body !== null && (!("errors" in body) || Array.isArray(body.errors)) && (!("data" in body) || typeof body.data === "object");
|
|
314
319
|
function glabGraphQL(run, cwd) {
|
|
315
|
-
return async (host,
|
|
316
|
-
const args = ["api", "graphql", "--hostname", host, "-f", `query=${
|
|
320
|
+
return async (host, query2, variables) => {
|
|
321
|
+
const args = ["api", "graphql", "--hostname", host, "-f", `query=${query2}`];
|
|
317
322
|
for (const [name, value] of Object.entries(variables))
|
|
318
323
|
args.push("-f", `${name}=${value}`);
|
|
319
324
|
const result = await run("glab", args, cwd);
|
|
@@ -381,10 +386,10 @@ function duoState(state) {
|
|
|
381
386
|
}
|
|
382
387
|
|
|
383
388
|
class GitLabForge {
|
|
384
|
-
graphql;
|
|
385
389
|
traits = gitlabTraits;
|
|
390
|
+
graphql;
|
|
386
391
|
constructor(graphql) {
|
|
387
|
-
this.graphql = graphql;
|
|
392
|
+
this.graphql = typed(graphql);
|
|
388
393
|
}
|
|
389
394
|
async findByBranch(repository) {
|
|
390
395
|
const { source, targets, sourceBranch } = repository;
|
|
@@ -395,7 +400,7 @@ class GitLabForge {
|
|
|
395
400
|
const seen = new Set;
|
|
396
401
|
const nodes = [];
|
|
397
402
|
targets.forEach((_, index) => {
|
|
398
|
-
for (const node of data
|
|
403
|
+
for (const node of data[`p${index}`]?.mergeRequests?.nodes ?? []) {
|
|
399
404
|
if (node.sourceProject?.fullPath.toLowerCase() !== sourcePath)
|
|
400
405
|
continue;
|
|
401
406
|
const key = `${node.targetProject.fullPath}!${node.iid}`;
|
|
@@ -414,7 +419,7 @@ class GitLabForge {
|
|
|
414
419
|
targets.forEach((target, index) => variables[`p${index}`] = target.path);
|
|
415
420
|
const data = await this.graphql(source.host, mergeRequestNumbersQuery(targets.length), variables);
|
|
416
421
|
const sourcePath = source.path.toLowerCase();
|
|
417
|
-
const nodes = targets.flatMap((_, index) => data
|
|
422
|
+
const nodes = targets.flatMap((_, index) => data[`p${index}`]?.mergeRequests?.nodes ?? []);
|
|
418
423
|
return nodes.filter((node) => node.sourceProject?.fullPath.toLowerCase() === sourcePath).sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""))[0]?.iid;
|
|
419
424
|
}
|
|
420
425
|
async sourceBranch(ref) {
|
|
@@ -422,7 +427,7 @@ class GitLabForge {
|
|
|
422
427
|
project: ref.project,
|
|
423
428
|
iid: ref.iid
|
|
424
429
|
});
|
|
425
|
-
const branch = data
|
|
430
|
+
const branch = data.project?.mergeRequest?.sourceBranch;
|
|
426
431
|
return typeof branch === "string" && branch ? branch : undefined;
|
|
427
432
|
}
|
|
428
433
|
async findByNumber(projects, iid) {
|
|
@@ -433,7 +438,7 @@ class GitLabForge {
|
|
|
433
438
|
projects.forEach((project, index) => variables[`p${index}`] = project.path);
|
|
434
439
|
const data = await this.graphql(host, mergeRequestQuery(projects.length), variables);
|
|
435
440
|
for (let index = 0;index < projects.length; index++) {
|
|
436
|
-
const node = data
|
|
441
|
+
const node = data[`p${index}`]?.mergeRequest;
|
|
437
442
|
if (node)
|
|
438
443
|
return this.normalize(host, node);
|
|
439
444
|
}
|
|
@@ -449,7 +454,7 @@ class GitLabForge {
|
|
|
449
454
|
iid: node.iid,
|
|
450
455
|
after: page.endCursor
|
|
451
456
|
});
|
|
452
|
-
const next = data
|
|
457
|
+
const next = data.project?.mergeRequest?.discussions;
|
|
453
458
|
if (!next)
|
|
454
459
|
break;
|
|
455
460
|
count += countUnresolved(next);
|
|
@@ -505,8 +510,8 @@ var fields = `number title url state isDraft mergeable reviewDecision headRefOid
|
|
|
505
510
|
} } } } }`;
|
|
506
511
|
var MAX_PAGES = 50;
|
|
507
512
|
function ghGraphQL(run, cwd) {
|
|
508
|
-
return async (host,
|
|
509
|
-
const args = ["api", "graphql", "--hostname", host, "-f", `query=${
|
|
513
|
+
return async (host, query2, variables) => {
|
|
514
|
+
const args = ["api", "graphql", "--hostname", host, "-f", `query=${query2}`];
|
|
510
515
|
for (const [name, value] of Object.entries(variables))
|
|
511
516
|
args.push(typeof value === "number" ? "-F" : "-f", `${name}=${value}`);
|
|
512
517
|
const result = await run("gh", args, cwd);
|
|
@@ -596,17 +601,17 @@ var githubTraits = {
|
|
|
596
601
|
};
|
|
597
602
|
|
|
598
603
|
class GitHubForge {
|
|
599
|
-
graphql;
|
|
600
604
|
traits = githubTraits;
|
|
605
|
+
graphql;
|
|
601
606
|
constructor(graphql) {
|
|
602
|
-
this.graphql = graphql;
|
|
607
|
+
this.graphql = typed(graphql);
|
|
603
608
|
}
|
|
604
609
|
async findByNumber(projects, number) {
|
|
605
610
|
for (const project of projects) {
|
|
606
611
|
const [owner, repo] = project.path.split("/");
|
|
607
|
-
const data = await this.graphql(project.host, `query($owner:String!,$repo:String!,$number:Int!) {
|
|
612
|
+
const data = await this.graphql(project.host, query(`query($owner:String!,$repo:String!,$number:Int!) {
|
|
608
613
|
repository(owner:$owner,name:$repo) { pullRequest(number:$number) { ${fields} } }
|
|
609
|
-
}
|
|
614
|
+
}`), { owner, repo, number: Number(number) });
|
|
610
615
|
const node = data.repository?.pullRequest;
|
|
611
616
|
if (node)
|
|
612
617
|
return this.normalize(project.host, node);
|
|
@@ -617,10 +622,10 @@ class GitHubForge {
|
|
|
617
622
|
let newest;
|
|
618
623
|
for (const target of repository.targets) {
|
|
619
624
|
const [owner, repo] = target.path.split("/");
|
|
620
|
-
const data = await this.graphql(target.host, `query($owner:String!,$repo:String!,$branch:String!) {
|
|
625
|
+
const data = await this.graphql(target.host, query(`query($owner:String!,$repo:String!,$branch:String!) {
|
|
621
626
|
repository(owner:$owner,name:$repo) { pullRequests(first:100,states:OPEN,headRefName:$branch,orderBy:{field:UPDATED_AT,direction:DESC}) {
|
|
622
627
|
nodes { number updatedAt headRepository { nameWithOwner } }
|
|
623
|
-
} } }
|
|
628
|
+
} } }`), { owner, repo, branch: repository.sourceBranch });
|
|
624
629
|
const nodes = data.repository?.pullRequests?.nodes ?? [];
|
|
625
630
|
for (const node of nodes) {
|
|
626
631
|
if (node.headRepository?.nameWithOwner.toLowerCase() !== repository.source.path.toLowerCase())
|
|
@@ -633,11 +638,10 @@ class GitHubForge {
|
|
|
633
638
|
}
|
|
634
639
|
async sourceBranch(ref) {
|
|
635
640
|
const [owner, repo] = ref.project.split("/");
|
|
636
|
-
const data = await this.graphql(ref.host, `query($owner:String!,$repo:String!,$number:Int!) {
|
|
641
|
+
const data = await this.graphql(ref.host, query(`query($owner:String!,$repo:String!,$number:Int!) {
|
|
637
642
|
repository(owner:$owner,name:$repo) { pullRequest(number:$number) { headRefName } }
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
return typeof branch === "string" && branch ? branch : undefined;
|
|
643
|
+
}`), { owner, repo, number: Number(ref.iid) });
|
|
644
|
+
return data.repository?.pullRequest?.headRefName || undefined;
|
|
641
645
|
}
|
|
642
646
|
async findByBranch(repository) {
|
|
643
647
|
const found = new Map;
|
|
@@ -646,10 +650,10 @@ class GitHubForge {
|
|
|
646
650
|
let after;
|
|
647
651
|
let complete = false;
|
|
648
652
|
for (let page = 0;page < MAX_PAGES; page++) {
|
|
649
|
-
const data = await this.graphql(target.host, `query($owner:String!,$repo:String!,$branch:String!${after ? ",$after:String!" : ""}) {
|
|
653
|
+
const data = await this.graphql(target.host, query(`query($owner:String!,$repo:String!,$branch:String!${after ? ",$after:String!" : ""}) {
|
|
650
654
|
repository(owner:$owner,name:$repo) { pullRequests(first:100,states:OPEN,headRefName:$branch${after ? ",after:$after" : ""}) {
|
|
651
655
|
nodes { ${fields} } pageInfo { hasNextPage endCursor }
|
|
652
|
-
} } }
|
|
656
|
+
} } }`), { owner, repo, branch: repository.sourceBranch, ...after ? { after } : {} });
|
|
653
657
|
const pulls = data.repository?.pullRequests;
|
|
654
658
|
if (!pulls)
|
|
655
659
|
throw new ForgeError("request", "GitHub repository unavailable");
|
|
@@ -677,10 +681,10 @@ class GitHubForge {
|
|
|
677
681
|
const threads = [...node.reviewThreads.nodes];
|
|
678
682
|
let page = node.reviewThreads.pageInfo;
|
|
679
683
|
for (let count = 1;page?.hasNextPage && page.endCursor && count < MAX_PAGES; count++) {
|
|
680
|
-
const data = await this.graphql(host, `query($owner:String!,$repo:String!,$number:Int!,$after:String!) {
|
|
684
|
+
const data = await this.graphql(host, query(`query($owner:String!,$repo:String!,$number:Int!,$after:String!) {
|
|
681
685
|
repository(owner:$owner,name:$repo) { pullRequest(number:$number) {
|
|
682
686
|
reviewThreads(first:100,after:$after) { nodes { isResolved } pageInfo { hasNextPage endCursor } }
|
|
683
|
-
} } }
|
|
687
|
+
} } }`), { owner, repo, number: node.number, after: page.endCursor });
|
|
684
688
|
const next = data.repository?.pullRequest?.reviewThreads;
|
|
685
689
|
if (!next)
|
|
686
690
|
break;
|
|
@@ -698,10 +702,10 @@ class GitHubForge {
|
|
|
698
702
|
const contexts = [...rollup?.nodes ?? []];
|
|
699
703
|
let cursor = rollup?.pageInfo;
|
|
700
704
|
for (let count = 1;cursor?.hasNextPage && cursor.endCursor && count < MAX_PAGES; count++) {
|
|
701
|
-
const data = await this.graphql(host, `query($owner:String!,$repo:String!,$oid:GitObjectID!,$after:String!) {
|
|
705
|
+
const data = await this.graphql(host, query(`query($owner:String!,$repo:String!,$oid:GitObjectID!,$after:String!) {
|
|
702
706
|
repository(owner:$owner,name:$repo) { object(oid:$oid) { ... on Commit { statusCheckRollup {
|
|
703
707
|
contexts(first:100,after:$after) { nodes { __typename ... on CheckRun { status conclusion detailsUrl } ... on StatusContext { state targetUrl } } pageInfo { hasNextPage endCursor } }
|
|
704
|
-
} } } } }
|
|
708
|
+
} } } } }`), { owner, repo, oid: node.headRefOid, after: cursor.endCursor });
|
|
705
709
|
const next = data.repository?.object?.statusCheckRollup?.contexts;
|
|
706
710
|
if (!next)
|
|
707
711
|
break;
|
|
@@ -910,11 +914,12 @@ var target = {
|
|
|
910
914
|
additionalProperties: false
|
|
911
915
|
};
|
|
912
916
|
var key = { type: "string" };
|
|
913
|
-
var snapshot = { type: "object" };
|
|
917
|
+
var snapshot = { type: "object", title: "Snapshot" };
|
|
914
918
|
var status = {
|
|
915
919
|
type: "object",
|
|
916
920
|
properties: { enabled: { type: "boolean" }, snapshot },
|
|
917
|
-
required: ["enabled", "snapshot"]
|
|
921
|
+
required: ["enabled", "snapshot"],
|
|
922
|
+
additionalProperties: false
|
|
918
923
|
};
|
|
919
924
|
var ForgeSessionRpc = {
|
|
920
925
|
id: "opencode-forgekeeper",
|
|
@@ -941,8 +946,12 @@ var ForgeSessionRpc = {
|
|
|
941
946
|
},
|
|
942
947
|
output: {
|
|
943
948
|
type: "object",
|
|
944
|
-
properties: {
|
|
945
|
-
|
|
949
|
+
properties: {
|
|
950
|
+
enabled: { type: "boolean" },
|
|
951
|
+
statuses: { type: "object", additionalProperties: snapshot }
|
|
952
|
+
},
|
|
953
|
+
required: ["enabled", "statuses"],
|
|
954
|
+
additionalProperties: false
|
|
946
955
|
}
|
|
947
956
|
},
|
|
948
957
|
release: {
|
|
@@ -952,7 +961,7 @@ var ForgeSessionRpc = {
|
|
|
952
961
|
required: ["clientID"],
|
|
953
962
|
additionalProperties: false
|
|
954
963
|
},
|
|
955
|
-
output: { type: "object" }
|
|
964
|
+
output: { type: "object", additionalProperties: false }
|
|
956
965
|
},
|
|
957
966
|
refresh: {
|
|
958
967
|
input: {
|
|
@@ -977,7 +986,8 @@ var ForgeSessionRpc = {
|
|
|
977
986
|
schema: {
|
|
978
987
|
type: "object",
|
|
979
988
|
properties: { directory: { type: "string" }, key, snapshot },
|
|
980
|
-
required: ["directory", "key", "snapshot"]
|
|
989
|
+
required: ["directory", "key", "snapshot"],
|
|
990
|
+
additionalProperties: false
|
|
981
991
|
}
|
|
982
992
|
},
|
|
983
993
|
notice: {
|
|
@@ -989,7 +999,8 @@ var ForgeSessionRpc = {
|
|
|
989
999
|
message: { type: "string" },
|
|
990
1000
|
variant: { type: "string", enum: ["info", "error"] }
|
|
991
1001
|
},
|
|
992
|
-
required: ["message", "variant"]
|
|
1002
|
+
required: ["message", "variant"],
|
|
1003
|
+
additionalProperties: false
|
|
993
1004
|
}
|
|
994
1005
|
}
|
|
995
1006
|
}
|
|
@@ -997,6 +1008,12 @@ var ForgeSessionRpc = {
|
|
|
997
1008
|
var HOME = "";
|
|
998
1009
|
var HEARTBEAT = 60000;
|
|
999
1010
|
var LEASE_TIME = 3 * HEARTBEAT;
|
|
1011
|
+
var implement = (handlers) => ({
|
|
1012
|
+
target: (input) => handlers.target(input),
|
|
1013
|
+
watch: (input) => handlers.watch(input),
|
|
1014
|
+
release: (input) => handlers.release(input),
|
|
1015
|
+
refresh: (input) => handlers.refresh(input)
|
|
1016
|
+
});
|
|
1000
1017
|
|
|
1001
1018
|
// packages/forgekeeper/src/automated-review-watch.ts
|
|
1002
1019
|
var requests = (lookup) => lookup?.kind === "found" ? lookup.requests : [];
|
|
@@ -1238,6 +1255,175 @@ function createFeedbackWatcher(options) {
|
|
|
1238
1255
|
};
|
|
1239
1256
|
}
|
|
1240
1257
|
|
|
1258
|
+
// packages/forgekeeper/src/store.ts
|
|
1259
|
+
class TargetLookupError extends Error {
|
|
1260
|
+
cause;
|
|
1261
|
+
constructor(cause) {
|
|
1262
|
+
super(cause instanceof Error ? cause.message : String(cause));
|
|
1263
|
+
this.cause = cause;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
function createStatusStore(options) {
|
|
1267
|
+
const now = options.now ?? Date.now;
|
|
1268
|
+
const setTimer = options.setTimer ?? ((callback, delay) => setTimeout(callback, delay));
|
|
1269
|
+
const clearTimer = options.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
1270
|
+
const interval = options.interval ?? 120000;
|
|
1271
|
+
const maxBackoff = options.maxBackoff ?? 900000;
|
|
1272
|
+
const minEventInterval = options.minEventInterval ?? 15000;
|
|
1273
|
+
const entries = new Map;
|
|
1274
|
+
let disposed = false;
|
|
1275
|
+
let revision = now() * 1000;
|
|
1276
|
+
const entry = (key2) => {
|
|
1277
|
+
let value = entries.get(key2);
|
|
1278
|
+
if (!value) {
|
|
1279
|
+
value = { snapshot: { loading: false }, refs: 0, failures: 0 };
|
|
1280
|
+
entries.set(key2, value);
|
|
1281
|
+
}
|
|
1282
|
+
return value;
|
|
1283
|
+
};
|
|
1284
|
+
const update = (key2, value, patch) => {
|
|
1285
|
+
value.snapshot = { ...value.snapshot, ...patch, revision: ++revision };
|
|
1286
|
+
if (!disposed)
|
|
1287
|
+
options.onChange(key2);
|
|
1288
|
+
};
|
|
1289
|
+
const cancelTimer = (value) => {
|
|
1290
|
+
if (value.timer === undefined)
|
|
1291
|
+
return;
|
|
1292
|
+
clearTimer(value.timer);
|
|
1293
|
+
value.timer = undefined;
|
|
1294
|
+
};
|
|
1295
|
+
const baseInterval = (value) => options.active?.(value.snapshot.lookup) ? options.activeInterval ?? interval : interval;
|
|
1296
|
+
const pollDelay = (value) => Math.min(baseInterval(value) * 2 ** value.failures, maxBackoff);
|
|
1297
|
+
const lastAttempt = (value) => Math.max(value.snapshot.fetchedAt ?? -Infinity, value.snapshot.error?.at ?? -Infinity);
|
|
1298
|
+
const schedule = (key2, value, delay = pollDelay(value)) => {
|
|
1299
|
+
cancelTimer(value);
|
|
1300
|
+
if (disposed || value.refs === 0)
|
|
1301
|
+
return;
|
|
1302
|
+
const lookup = value.snapshot.lookup;
|
|
1303
|
+
if (!value.snapshot.error && (lookup?.kind !== "found" || lookup.requests.length === 0))
|
|
1304
|
+
return;
|
|
1305
|
+
value.timer = setTimer(() => {
|
|
1306
|
+
value.timer = undefined;
|
|
1307
|
+
refresh(key2);
|
|
1308
|
+
}, delay);
|
|
1309
|
+
};
|
|
1310
|
+
function refresh(key2) {
|
|
1311
|
+
if (disposed)
|
|
1312
|
+
return Promise.resolve({ loading: false });
|
|
1313
|
+
const value = entry(key2);
|
|
1314
|
+
if (value.inflight)
|
|
1315
|
+
return value.inflight;
|
|
1316
|
+
value.stale = false;
|
|
1317
|
+
update(key2, value, { loading: true });
|
|
1318
|
+
value.inflight = options.load(key2).then((lookup) => {
|
|
1319
|
+
if (disposed || value.invalidated)
|
|
1320
|
+
return;
|
|
1321
|
+
const previous = value.snapshot.lookup;
|
|
1322
|
+
value.failures = 0;
|
|
1323
|
+
update(key2, value, {
|
|
1324
|
+
lookup,
|
|
1325
|
+
fetchedAt: now(),
|
|
1326
|
+
error: undefined,
|
|
1327
|
+
targeted: lookup.explicitTarget === true ? true : undefined,
|
|
1328
|
+
loading: false
|
|
1329
|
+
});
|
|
1330
|
+
try {
|
|
1331
|
+
if (!disposed)
|
|
1332
|
+
options.onLoad?.(key2, previous, lookup);
|
|
1333
|
+
} catch (error) {
|
|
1334
|
+
try {
|
|
1335
|
+
options.onLoadError?.(error);
|
|
1336
|
+
} catch {}
|
|
1337
|
+
}
|
|
1338
|
+
}, (error) => {
|
|
1339
|
+
if (disposed || value.invalidated)
|
|
1340
|
+
return;
|
|
1341
|
+
value.failures++;
|
|
1342
|
+
const target2 = error instanceof TargetLookupError;
|
|
1343
|
+
const cause = target2 ? error.cause : error;
|
|
1344
|
+
update(key2, value, {
|
|
1345
|
+
loading: false,
|
|
1346
|
+
error: {
|
|
1347
|
+
kind: cause instanceof ForgeError ? cause.kind : "request",
|
|
1348
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
1349
|
+
at: now()
|
|
1350
|
+
},
|
|
1351
|
+
...target2 ? { targeted: true } : {}
|
|
1352
|
+
});
|
|
1353
|
+
}).then(() => {
|
|
1354
|
+
value.inflight = undefined;
|
|
1355
|
+
if (value.invalidated && !disposed) {
|
|
1356
|
+
value.invalidated = false;
|
|
1357
|
+
return refresh(key2);
|
|
1358
|
+
}
|
|
1359
|
+
schedule(key2, value);
|
|
1360
|
+
return value.snapshot;
|
|
1361
|
+
});
|
|
1362
|
+
return value.inflight;
|
|
1363
|
+
}
|
|
1364
|
+
return {
|
|
1365
|
+
get(key2) {
|
|
1366
|
+
return entries.get(key2)?.snapshot ?? { loading: false };
|
|
1367
|
+
},
|
|
1368
|
+
acquire(key2) {
|
|
1369
|
+
const value = entry(key2);
|
|
1370
|
+
value.refs++;
|
|
1371
|
+
if (value.refs === 1 && !value.inflight) {
|
|
1372
|
+
const remaining = lastAttempt(value) + pollDelay(value) - now();
|
|
1373
|
+
if (value.stale || remaining <= 0)
|
|
1374
|
+
refresh(key2);
|
|
1375
|
+
else
|
|
1376
|
+
schedule(key2, value, remaining);
|
|
1377
|
+
}
|
|
1378
|
+
let released = false;
|
|
1379
|
+
return () => {
|
|
1380
|
+
if (released)
|
|
1381
|
+
return;
|
|
1382
|
+
released = true;
|
|
1383
|
+
value.refs--;
|
|
1384
|
+
if (value.refs === 0)
|
|
1385
|
+
cancelTimer(value);
|
|
1386
|
+
};
|
|
1387
|
+
},
|
|
1388
|
+
refresh,
|
|
1389
|
+
keys() {
|
|
1390
|
+
return [...entries.keys()];
|
|
1391
|
+
},
|
|
1392
|
+
notify(key2) {
|
|
1393
|
+
const value = entries.get(key2);
|
|
1394
|
+
if (!value || value.refs === 0 || value.inflight)
|
|
1395
|
+
return;
|
|
1396
|
+
const last = Math.max(value.snapshot.fetchedAt ?? 0, value.snapshot.error?.at ?? 0);
|
|
1397
|
+
if (now() - last < minEventInterval)
|
|
1398
|
+
return;
|
|
1399
|
+
refresh(key2);
|
|
1400
|
+
},
|
|
1401
|
+
invalidate(key2) {
|
|
1402
|
+
const value = entries.get(key2);
|
|
1403
|
+
if (!value)
|
|
1404
|
+
return;
|
|
1405
|
+
if (value.inflight)
|
|
1406
|
+
value.invalidated = true;
|
|
1407
|
+
else if (value.refs > 0)
|
|
1408
|
+
refresh(key2);
|
|
1409
|
+
else
|
|
1410
|
+
value.stale = true;
|
|
1411
|
+
},
|
|
1412
|
+
setTargeted(key2, targeted) {
|
|
1413
|
+
const value = entries.get(key2);
|
|
1414
|
+
if (!value || disposed || value.snapshot.targeted === true === targeted)
|
|
1415
|
+
return;
|
|
1416
|
+
update(key2, value, { targeted: targeted ? true : undefined });
|
|
1417
|
+
},
|
|
1418
|
+
dispose() {
|
|
1419
|
+
disposed = true;
|
|
1420
|
+
for (const value of entries.values())
|
|
1421
|
+
cancelTimer(value);
|
|
1422
|
+
entries.clear();
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1241
1427
|
// packages/forgekeeper/src/target.ts
|
|
1242
1428
|
var MAX_TARGETS = 50;
|
|
1243
1429
|
var PREFIX_REFERENCES = 4;
|
|
@@ -1449,11 +1635,12 @@ async function locateTargets(forges, directory, targets, previous) {
|
|
|
1449
1635
|
});
|
|
1450
1636
|
});
|
|
1451
1637
|
if (error !== undefined && !outcomes.some((outcome) => ("request" in outcome)))
|
|
1452
|
-
throw error;
|
|
1638
|
+
throw new TargetLookupError(error);
|
|
1453
1639
|
if (found.length === 0)
|
|
1454
1640
|
return {
|
|
1455
1641
|
kind: "none",
|
|
1456
|
-
reason: requests2.length === 1 ? failed[0].reason : "No session target PR/MR could be resolved"
|
|
1642
|
+
reason: requests2.length === 1 ? failed[0].reason : "No session target PR/MR could be resolved",
|
|
1643
|
+
explicitTarget: true
|
|
1457
1644
|
};
|
|
1458
1645
|
return {
|
|
1459
1646
|
kind: "found",
|
|
@@ -1499,154 +1686,6 @@ async function locate(forges, hints) {
|
|
|
1499
1686
|
};
|
|
1500
1687
|
}
|
|
1501
1688
|
|
|
1502
|
-
// packages/forgekeeper/src/store.ts
|
|
1503
|
-
function createStatusStore(options) {
|
|
1504
|
-
const now = options.now ?? Date.now;
|
|
1505
|
-
const setTimer = options.setTimer ?? ((callback, delay) => setTimeout(callback, delay));
|
|
1506
|
-
const clearTimer = options.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
1507
|
-
const interval = options.interval ?? 120000;
|
|
1508
|
-
const maxBackoff = options.maxBackoff ?? 900000;
|
|
1509
|
-
const minEventInterval = options.minEventInterval ?? 15000;
|
|
1510
|
-
const entries = new Map;
|
|
1511
|
-
let disposed = false;
|
|
1512
|
-
let revision = now() * 1000;
|
|
1513
|
-
const entry = (key2) => {
|
|
1514
|
-
let value = entries.get(key2);
|
|
1515
|
-
if (!value) {
|
|
1516
|
-
value = { snapshot: { loading: false }, refs: 0, failures: 0 };
|
|
1517
|
-
entries.set(key2, value);
|
|
1518
|
-
}
|
|
1519
|
-
return value;
|
|
1520
|
-
};
|
|
1521
|
-
const update = (key2, value, patch) => {
|
|
1522
|
-
value.snapshot = { ...value.snapshot, ...patch, revision: ++revision };
|
|
1523
|
-
if (!disposed)
|
|
1524
|
-
options.onChange(key2);
|
|
1525
|
-
};
|
|
1526
|
-
const cancelTimer = (value) => {
|
|
1527
|
-
if (value.timer === undefined)
|
|
1528
|
-
return;
|
|
1529
|
-
clearTimer(value.timer);
|
|
1530
|
-
value.timer = undefined;
|
|
1531
|
-
};
|
|
1532
|
-
const baseInterval = (value) => options.active?.(value.snapshot.lookup) ? options.activeInterval ?? interval : interval;
|
|
1533
|
-
const pollDelay = (value) => Math.min(baseInterval(value) * 2 ** value.failures, maxBackoff);
|
|
1534
|
-
const lastAttempt = (value) => Math.max(value.snapshot.fetchedAt ?? -Infinity, value.snapshot.error?.at ?? -Infinity);
|
|
1535
|
-
const schedule = (key2, value, delay = pollDelay(value)) => {
|
|
1536
|
-
cancelTimer(value);
|
|
1537
|
-
if (disposed || value.refs === 0)
|
|
1538
|
-
return;
|
|
1539
|
-
const lookup = value.snapshot.lookup;
|
|
1540
|
-
if (!value.snapshot.error && (lookup?.kind !== "found" || lookup.requests.length === 0))
|
|
1541
|
-
return;
|
|
1542
|
-
value.timer = setTimer(() => {
|
|
1543
|
-
value.timer = undefined;
|
|
1544
|
-
refresh(key2);
|
|
1545
|
-
}, delay);
|
|
1546
|
-
};
|
|
1547
|
-
function refresh(key2) {
|
|
1548
|
-
if (disposed)
|
|
1549
|
-
return Promise.resolve({ loading: false });
|
|
1550
|
-
const value = entry(key2);
|
|
1551
|
-
if (value.inflight)
|
|
1552
|
-
return value.inflight;
|
|
1553
|
-
value.stale = false;
|
|
1554
|
-
update(key2, value, { loading: true });
|
|
1555
|
-
value.inflight = options.load(key2).then((lookup) => {
|
|
1556
|
-
if (disposed || value.invalidated)
|
|
1557
|
-
return;
|
|
1558
|
-
const previous = value.snapshot.lookup;
|
|
1559
|
-
value.failures = 0;
|
|
1560
|
-
update(key2, value, { lookup, fetchedAt: now(), error: undefined, loading: false });
|
|
1561
|
-
try {
|
|
1562
|
-
if (!disposed)
|
|
1563
|
-
options.onLoad?.(key2, previous, lookup);
|
|
1564
|
-
} catch (error) {
|
|
1565
|
-
try {
|
|
1566
|
-
options.onLoadError?.(error);
|
|
1567
|
-
} catch {}
|
|
1568
|
-
}
|
|
1569
|
-
}, (error) => {
|
|
1570
|
-
if (disposed || value.invalidated)
|
|
1571
|
-
return;
|
|
1572
|
-
value.failures++;
|
|
1573
|
-
const known = error instanceof ForgeError;
|
|
1574
|
-
update(key2, value, {
|
|
1575
|
-
loading: false,
|
|
1576
|
-
error: {
|
|
1577
|
-
kind: known ? error.kind : "request",
|
|
1578
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1579
|
-
at: now()
|
|
1580
|
-
}
|
|
1581
|
-
});
|
|
1582
|
-
}).then(() => {
|
|
1583
|
-
value.inflight = undefined;
|
|
1584
|
-
if (value.invalidated && !disposed) {
|
|
1585
|
-
value.invalidated = false;
|
|
1586
|
-
return refresh(key2);
|
|
1587
|
-
}
|
|
1588
|
-
schedule(key2, value);
|
|
1589
|
-
return value.snapshot;
|
|
1590
|
-
});
|
|
1591
|
-
return value.inflight;
|
|
1592
|
-
}
|
|
1593
|
-
return {
|
|
1594
|
-
get(key2) {
|
|
1595
|
-
return entries.get(key2)?.snapshot ?? { loading: false };
|
|
1596
|
-
},
|
|
1597
|
-
acquire(key2) {
|
|
1598
|
-
const value = entry(key2);
|
|
1599
|
-
value.refs++;
|
|
1600
|
-
if (value.refs === 1 && !value.inflight) {
|
|
1601
|
-
const remaining = lastAttempt(value) + pollDelay(value) - now();
|
|
1602
|
-
if (value.stale || remaining <= 0)
|
|
1603
|
-
refresh(key2);
|
|
1604
|
-
else
|
|
1605
|
-
schedule(key2, value, remaining);
|
|
1606
|
-
}
|
|
1607
|
-
let released = false;
|
|
1608
|
-
return () => {
|
|
1609
|
-
if (released)
|
|
1610
|
-
return;
|
|
1611
|
-
released = true;
|
|
1612
|
-
value.refs--;
|
|
1613
|
-
if (value.refs === 0)
|
|
1614
|
-
cancelTimer(value);
|
|
1615
|
-
};
|
|
1616
|
-
},
|
|
1617
|
-
refresh,
|
|
1618
|
-
keys() {
|
|
1619
|
-
return [...entries.keys()];
|
|
1620
|
-
},
|
|
1621
|
-
notify(key2) {
|
|
1622
|
-
const value = entries.get(key2);
|
|
1623
|
-
if (!value || value.refs === 0 || value.inflight)
|
|
1624
|
-
return;
|
|
1625
|
-
const last = Math.max(value.snapshot.fetchedAt ?? 0, value.snapshot.error?.at ?? 0);
|
|
1626
|
-
if (now() - last < minEventInterval)
|
|
1627
|
-
return;
|
|
1628
|
-
refresh(key2);
|
|
1629
|
-
},
|
|
1630
|
-
invalidate(key2) {
|
|
1631
|
-
const value = entries.get(key2);
|
|
1632
|
-
if (!value)
|
|
1633
|
-
return;
|
|
1634
|
-
if (value.inflight)
|
|
1635
|
-
value.invalidated = true;
|
|
1636
|
-
else if (value.refs > 0)
|
|
1637
|
-
refresh(key2);
|
|
1638
|
-
else
|
|
1639
|
-
value.stale = true;
|
|
1640
|
-
},
|
|
1641
|
-
dispose() {
|
|
1642
|
-
disposed = true;
|
|
1643
|
-
for (const value of entries.values())
|
|
1644
|
-
cancelTimer(value);
|
|
1645
|
-
entries.clear();
|
|
1646
|
-
}
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
1689
|
// packages/forgekeeper/src/status-service.ts
|
|
1651
1690
|
function createStatusService(options) {
|
|
1652
1691
|
const now = options.now ?? Date.now;
|
|
@@ -1774,6 +1813,7 @@ function createStatusService(options) {
|
|
|
1774
1813
|
if (!disposed && !holds.has(key2) && liveLeases(key2).length > 0)
|
|
1775
1814
|
store.refresh(key2);
|
|
1776
1815
|
},
|
|
1816
|
+
setTargeted: (key2, targeted) => store.setTargeted(key2, targeted),
|
|
1777
1817
|
notify: (key2) => store.notify(key2),
|
|
1778
1818
|
keys: () => store.keys(),
|
|
1779
1819
|
polling: (key2) => holds.has(key2),
|
|
@@ -1978,7 +2018,11 @@ async function setupStatus(ctx, settings, deps, host) {
|
|
|
1978
2018
|
});
|
|
1979
2019
|
return {
|
|
1980
2020
|
service,
|
|
1981
|
-
targetsChanged
|
|
2021
|
+
targetsChanged(sessionID, targets) {
|
|
2022
|
+
const classified = classifyTargets(targetOutput(targets)) ?? [];
|
|
2023
|
+
service.setTargeted(sessionID, classified.some((target2) => target2.kind === "merge-request"));
|
|
2024
|
+
service.invalidate(sessionID);
|
|
2025
|
+
},
|
|
1982
2026
|
async titleChanged(sessionID) {
|
|
1983
2027
|
if (!service.keys().includes(sessionID))
|
|
1984
2028
|
return;
|
|
@@ -2112,24 +2156,15 @@ async function setupServer(ctx, lookups, status2) {
|
|
|
2112
2156
|
}
|
|
2113
2157
|
};
|
|
2114
2158
|
let watcher;
|
|
2115
|
-
const rpc = await ctx.rpc.register(ForgeSessionRpc, {
|
|
2116
|
-
target: async (
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
watch: async (input) => {
|
|
2121
|
-
const { clientID, keys, visible } = input;
|
|
2122
|
-
return watcher ? { enabled: true, statuses: json(watcher.service.watch(clientID, keys, visible)) } : { enabled: false, statuses: {} };
|
|
2123
|
-
},
|
|
2124
|
-
release: async (input) => {
|
|
2125
|
-
watcher?.service.release(input.clientID);
|
|
2159
|
+
const rpc = await ctx.rpc.register(ForgeSessionRpc, implement({
|
|
2160
|
+
target: async ({ sessionID }) => targetOutput((await stateFor(sessionID)).targets),
|
|
2161
|
+
watch: async ({ clientID, keys, visible }) => watcher ? { enabled: true, statuses: json(watcher.service.watch(clientID, keys, visible)) } : { enabled: false, statuses: {} },
|
|
2162
|
+
release: async ({ clientID }) => {
|
|
2163
|
+
watcher?.service.release(clientID);
|
|
2126
2164
|
return {};
|
|
2127
2165
|
},
|
|
2128
|
-
refresh: async (
|
|
2129
|
-
|
|
2130
|
-
return watcher ? { enabled: true, snapshot: json(await watcher.service.refresh(key2)) } : DISABLED;
|
|
2131
|
-
}
|
|
2132
|
-
});
|
|
2166
|
+
refresh: async ({ key: key2 }) => watcher ? { enabled: true, snapshot: json(await watcher.service.refresh(key2)) } : DISABLED
|
|
2167
|
+
}));
|
|
2133
2168
|
const settings = statusOptions(ctx.options);
|
|
2134
2169
|
if (status2 && settings.enabled) {
|
|
2135
2170
|
watcher = await setupStatus(ctx, settings, status2, {
|
|
@@ -2235,7 +2270,7 @@ async function setupServer(ctx, lookups, status2) {
|
|
|
2235
2270
|
targets: result.targets,
|
|
2236
2271
|
...state.prefix ? { prefix: state.prefix } : {}
|
|
2237
2272
|
});
|
|
2238
|
-
watcher?.targetsChanged(sessionID);
|
|
2273
|
+
watcher?.targetsChanged(sessionID, result.targets);
|
|
2239
2274
|
await rpc.events.emit("targetChanged", { sessionID, ...targetOutput(result.targets) });
|
|
2240
2275
|
await update(sessionID);
|
|
2241
2276
|
});
|