ework-web 0.10.96 → 0.10.98
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/package.json +1 -1
- package/src/store.ts +1 -0
- package/src/upstream-sync.ts +47 -10
- package/src/webhooks.ts +2 -0
package/package.json
CHANGED
package/src/store.ts
CHANGED
package/src/upstream-sync.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getIssueByUpstreamNumber,
|
|
7
7
|
getCommentByUpstreamId,
|
|
8
8
|
createIssue,
|
|
9
|
+
ensureUser,
|
|
9
10
|
postComment,
|
|
10
11
|
editIssue,
|
|
11
12
|
updateUpstreamSyncState,
|
|
@@ -23,6 +24,7 @@ interface GiteaIssue {
|
|
|
23
24
|
user: { login: string } | null;
|
|
24
25
|
created_at: string;
|
|
25
26
|
updated_at: string;
|
|
27
|
+
pull_request?: unknown;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
interface GiteaComment {
|
|
@@ -42,6 +44,12 @@ export interface UpstreamSyncPollResult {
|
|
|
42
44
|
|
|
43
45
|
const GITEA_COMMENT_ISSUE_RE = /\/issues\/(\d+)$/;
|
|
44
46
|
|
|
47
|
+
// GitHub CI/actions identities end in [bot]; their comments must not wake
|
|
48
|
+
// the engine, so they are imported as bot-kind users.
|
|
49
|
+
function fromGithubBot(login: string | undefined): boolean {
|
|
50
|
+
return typeof login === "string" && /\[bot\]$/i.test(login.trim());
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
function upstreamIssueNumberFromComment(gc: GiteaComment): number | null {
|
|
46
54
|
const m = (gc.issue_url ?? "").match(GITEA_COMMENT_ISSUE_RE);
|
|
47
55
|
return m ? Number(m[1]) : null;
|
|
@@ -71,7 +79,11 @@ export class UpstreamSync {
|
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
private api(path: string): string {
|
|
74
|
-
|
|
82
|
+
const base = /github\.com$/i.test(new URL(this.sync.base_url).host)
|
|
83
|
+
? "https://api.github.com"
|
|
84
|
+
: this.sync.base_url;
|
|
85
|
+
const prefix = base === "https://api.github.com" ? "" : "/api/v1";
|
|
86
|
+
return `${base}${prefix}/repos/${this.sync.upstream_owner}/${this.sync.upstream_repo}${path}`;
|
|
75
87
|
}
|
|
76
88
|
|
|
77
89
|
private async fetchJson<T>(path: string): Promise<T | null> {
|
|
@@ -85,14 +97,23 @@ export class UpstreamSync {
|
|
|
85
97
|
// comments silently so the AI isn't woken by a flood of historical entries.
|
|
86
98
|
// Live polls (cursor set) emit webhooks so new upstream activity reaches
|
|
87
99
|
// the daemon exactly like locally-created content.
|
|
100
|
+
private get isGithub(): boolean {
|
|
101
|
+
try {
|
|
102
|
+
return /github\.com$/i.test(new URL(this.sync.base_url).host);
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
88
108
|
private get isBackfill(): boolean {
|
|
89
109
|
return this.sync.issue_cursor === null;
|
|
90
110
|
}
|
|
91
111
|
|
|
92
112
|
private async importIssue(gi: GiteaIssue, emit: boolean): Promise<void> {
|
|
113
|
+
await ensureUser(gi.user?.login ?? "upstream", fromGithubBot(gi.user?.login) ? "bot" : "human");
|
|
93
114
|
await createIssue(
|
|
94
115
|
this.project.id,
|
|
95
|
-
gi.title || `#${gi.number}
|
|
116
|
+
(gi.pull_request ? "[PR] " : "") + (gi.title || `#${gi.number}`),
|
|
96
117
|
gi.body ?? "",
|
|
97
118
|
gi.user?.login ?? "upstream",
|
|
98
119
|
{
|
|
@@ -119,14 +140,21 @@ export class UpstreamSync {
|
|
|
119
140
|
}
|
|
120
141
|
|
|
121
142
|
private async importIssueComments(gi: GiteaIssue, emit: boolean): Promise<number> {
|
|
122
|
-
const comments = await this.fetchJson<GiteaComment[]>(
|
|
143
|
+
const comments = await this.fetchJson<GiteaComment[]>(this.isGithub
|
|
144
|
+
? `/issues/${gi.number}/comments?per_page=50`
|
|
145
|
+
: `/issues/${gi.number}/comments?limit=50&order=asc`);
|
|
123
146
|
if (!comments) return 0;
|
|
124
147
|
const local = await getIssueByUpstreamNumber(this.project.id, gi.number);
|
|
125
148
|
if (!local) return 0;
|
|
126
149
|
let n = 0;
|
|
127
150
|
for (const gc of comments) {
|
|
128
151
|
if (await getCommentByUpstreamId(gc.id)) continue;
|
|
129
|
-
|
|
152
|
+
// write-back comments carry this marker; importing them would duplicate locally
|
|
153
|
+
if ((gc.body ?? "").includes("<!-- ework-mirror -->")) continue;
|
|
154
|
+
const gcAuthor = gc.user?.login ?? "upstream";
|
|
155
|
+
// ensure the kind before postComment's internal user creation runs
|
|
156
|
+
await ensureUser(gcAuthor, fromGithubBot(gcAuthor) ? "bot" : "human");
|
|
157
|
+
const row = await postComment(local.id, gc.body ?? "", gcAuthor, {
|
|
130
158
|
createdAt: gc.created_at,
|
|
131
159
|
updatedAt: gc.updated_at,
|
|
132
160
|
upstreamCommentId: gc.id,
|
|
@@ -142,7 +170,9 @@ export class UpstreamSync {
|
|
|
142
170
|
let cursor: string | null = null;
|
|
143
171
|
for (let page = 1; page <= 20; page++) {
|
|
144
172
|
const issues = await this.fetchJson<GiteaIssue[]>(
|
|
145
|
-
|
|
173
|
+
this.isGithub
|
|
174
|
+
? `/issues?state=open&per_page=50&page=${page}&sort=created&direction=asc`
|
|
175
|
+
: `/issues?state=open&type=issues&limit=50&page=${page}&sort=created&order=asc`
|
|
146
176
|
);
|
|
147
177
|
if (!issues || issues.length === 0) break;
|
|
148
178
|
for (const gi of issues) {
|
|
@@ -166,7 +196,9 @@ export class UpstreamSync {
|
|
|
166
196
|
private async liveOnce(): Promise<UpstreamSyncPollResult> {
|
|
167
197
|
const result: UpstreamSyncPollResult = { issuesImported: 0, issuesUpdated: 0, commentsImported: 0 };
|
|
168
198
|
const issues = await this.fetchJson<GiteaIssue[]>(
|
|
169
|
-
|
|
199
|
+
this.isGithub
|
|
200
|
+
? `/issues?state=all&per_page=30&sort=updated&direction=desc`
|
|
201
|
+
: `/issues?state=all&type=issues&limit=30&sort=updated&order=desc`
|
|
170
202
|
);
|
|
171
203
|
let issueCursor = this.sync.issue_cursor;
|
|
172
204
|
if (issues) {
|
|
@@ -186,18 +218,23 @@ export class UpstreamSync {
|
|
|
186
218
|
|
|
187
219
|
let commentCursor = this.sync.comment_cursor;
|
|
188
220
|
const comments = await this.fetchJson<GiteaComment[]>(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
221
|
+
this.isGithub
|
|
222
|
+
? `/issues/comments?per_page=100${this.sync.comment_cursor ? `&since=${encodeURIComponent(this.sync.comment_cursor)}` : ""}`
|
|
223
|
+
: `/issues/comments?limit=50&sort=updated&order=asc${
|
|
224
|
+
this.sync.comment_cursor ? `&since=${encodeURIComponent(this.sync.comment_cursor)}` : ""
|
|
225
|
+
}`
|
|
192
226
|
);
|
|
193
227
|
if (comments) {
|
|
194
228
|
for (const gc of comments) {
|
|
195
229
|
if (await getCommentByUpstreamId(gc.id)) continue;
|
|
230
|
+
if ((gc.body ?? "").includes("<!-- ework-mirror -->")) continue;
|
|
196
231
|
const upstreamNumber = upstreamIssueNumberFromComment(gc);
|
|
197
232
|
if (!upstreamNumber) continue;
|
|
198
233
|
const local = await getIssueByUpstreamNumber(this.project.id, upstreamNumber);
|
|
199
234
|
if (!local) continue;
|
|
200
|
-
const
|
|
235
|
+
const gcAuthor = gc.user?.login ?? "upstream";
|
|
236
|
+
await ensureUser(gcAuthor, fromGithubBot(gcAuthor) ? "bot" : "human");
|
|
237
|
+
const row = await postComment(local.id, gc.body ?? "", gcAuthor, {
|
|
201
238
|
createdAt: gc.created_at,
|
|
202
239
|
updatedAt: gc.updated_at,
|
|
203
240
|
upstreamCommentId: gc.id,
|
package/src/webhooks.ts
CHANGED
|
@@ -277,6 +277,7 @@ interface PayloadIssue {
|
|
|
277
277
|
url: string;
|
|
278
278
|
html_url: string;
|
|
279
279
|
number: number;
|
|
280
|
+
upstream_issue_number?: number | null;
|
|
280
281
|
title: string;
|
|
281
282
|
body: string;
|
|
282
283
|
labels: PayloadLabel[];
|
|
@@ -429,6 +430,7 @@ function buildIssue(
|
|
|
429
430
|
url: `${origin}/api/v1/repos/${encodeURIComponent(project.owner)}/${encodeURIComponent(project.name)}/issues/${issue.number}`,
|
|
430
431
|
html_url: issueUrl,
|
|
431
432
|
number: issue.number,
|
|
433
|
+
upstream_issue_number: issue.upstream_issue_number ?? null,
|
|
432
434
|
title: issue.title,
|
|
433
435
|
body: issue.body ?? "",
|
|
434
436
|
labels,
|