ework-web 0.10.95 → 0.10.97
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 +32 -8
- package/src/views/issueThread.ts +20 -3
- package/src/webhooks.ts +2 -0
package/package.json
CHANGED
package/src/store.ts
CHANGED
package/src/upstream-sync.ts
CHANGED
|
@@ -23,6 +23,7 @@ interface GiteaIssue {
|
|
|
23
23
|
user: { login: string } | null;
|
|
24
24
|
created_at: string;
|
|
25
25
|
updated_at: string;
|
|
26
|
+
pull_request?: unknown;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
interface GiteaComment {
|
|
@@ -71,7 +72,11 @@ export class UpstreamSync {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
private api(path: string): string {
|
|
74
|
-
|
|
75
|
+
const base = /github\.com$/i.test(new URL(this.sync.base_url).host)
|
|
76
|
+
? "https://api.github.com"
|
|
77
|
+
: this.sync.base_url;
|
|
78
|
+
const prefix = base === "https://api.github.com" ? "" : "/api/v1";
|
|
79
|
+
return `${base}${prefix}/repos/${this.sync.upstream_owner}/${this.sync.upstream_repo}${path}`;
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
private async fetchJson<T>(path: string): Promise<T | null> {
|
|
@@ -85,6 +90,14 @@ export class UpstreamSync {
|
|
|
85
90
|
// comments silently so the AI isn't woken by a flood of historical entries.
|
|
86
91
|
// Live polls (cursor set) emit webhooks so new upstream activity reaches
|
|
87
92
|
// the daemon exactly like locally-created content.
|
|
93
|
+
private get isGithub(): boolean {
|
|
94
|
+
try {
|
|
95
|
+
return /github\.com$/i.test(new URL(this.sync.base_url).host);
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
private get isBackfill(): boolean {
|
|
89
102
|
return this.sync.issue_cursor === null;
|
|
90
103
|
}
|
|
@@ -92,7 +105,7 @@ export class UpstreamSync {
|
|
|
92
105
|
private async importIssue(gi: GiteaIssue, emit: boolean): Promise<void> {
|
|
93
106
|
await createIssue(
|
|
94
107
|
this.project.id,
|
|
95
|
-
gi.title || `#${gi.number}
|
|
108
|
+
(gi.pull_request ? "[PR] " : "") + (gi.title || `#${gi.number}`),
|
|
96
109
|
gi.body ?? "",
|
|
97
110
|
gi.user?.login ?? "upstream",
|
|
98
111
|
{
|
|
@@ -119,13 +132,17 @@ export class UpstreamSync {
|
|
|
119
132
|
}
|
|
120
133
|
|
|
121
134
|
private async importIssueComments(gi: GiteaIssue, emit: boolean): Promise<number> {
|
|
122
|
-
const comments = await this.fetchJson<GiteaComment[]>(
|
|
135
|
+
const comments = await this.fetchJson<GiteaComment[]>(this.isGithub
|
|
136
|
+
? `/issues/${gi.number}/comments?per_page=50`
|
|
137
|
+
: `/issues/${gi.number}/comments?limit=50&order=asc`);
|
|
123
138
|
if (!comments) return 0;
|
|
124
139
|
const local = await getIssueByUpstreamNumber(this.project.id, gi.number);
|
|
125
140
|
if (!local) return 0;
|
|
126
141
|
let n = 0;
|
|
127
142
|
for (const gc of comments) {
|
|
128
143
|
if (await getCommentByUpstreamId(gc.id)) continue;
|
|
144
|
+
// write-back comments carry this marker; importing them would duplicate locally
|
|
145
|
+
if ((gc.body ?? "").includes("<!-- ework-mirror -->")) continue;
|
|
129
146
|
const row = await postComment(local.id, gc.body ?? "", gc.user?.login ?? "upstream", {
|
|
130
147
|
createdAt: gc.created_at,
|
|
131
148
|
updatedAt: gc.updated_at,
|
|
@@ -142,7 +159,9 @@ export class UpstreamSync {
|
|
|
142
159
|
let cursor: string | null = null;
|
|
143
160
|
for (let page = 1; page <= 20; page++) {
|
|
144
161
|
const issues = await this.fetchJson<GiteaIssue[]>(
|
|
145
|
-
|
|
162
|
+
this.isGithub
|
|
163
|
+
? `/issues?state=open&per_page=50&page=${page}&sort=created&direction=asc`
|
|
164
|
+
: `/issues?state=open&type=issues&limit=50&page=${page}&sort=created&order=asc`
|
|
146
165
|
);
|
|
147
166
|
if (!issues || issues.length === 0) break;
|
|
148
167
|
for (const gi of issues) {
|
|
@@ -166,7 +185,9 @@ export class UpstreamSync {
|
|
|
166
185
|
private async liveOnce(): Promise<UpstreamSyncPollResult> {
|
|
167
186
|
const result: UpstreamSyncPollResult = { issuesImported: 0, issuesUpdated: 0, commentsImported: 0 };
|
|
168
187
|
const issues = await this.fetchJson<GiteaIssue[]>(
|
|
169
|
-
|
|
188
|
+
this.isGithub
|
|
189
|
+
? `/issues?state=all&per_page=30&sort=updated&direction=desc`
|
|
190
|
+
: `/issues?state=all&type=issues&limit=30&sort=updated&order=desc`
|
|
170
191
|
);
|
|
171
192
|
let issueCursor = this.sync.issue_cursor;
|
|
172
193
|
if (issues) {
|
|
@@ -186,13 +207,16 @@ export class UpstreamSync {
|
|
|
186
207
|
|
|
187
208
|
let commentCursor = this.sync.comment_cursor;
|
|
188
209
|
const comments = await this.fetchJson<GiteaComment[]>(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
210
|
+
this.isGithub
|
|
211
|
+
? `/issues/comments?per_page=100${this.sync.comment_cursor ? `&since=${encodeURIComponent(this.sync.comment_cursor)}` : ""}`
|
|
212
|
+
: `/issues/comments?limit=50&sort=updated&order=asc${
|
|
213
|
+
this.sync.comment_cursor ? `&since=${encodeURIComponent(this.sync.comment_cursor)}` : ""
|
|
214
|
+
}`
|
|
192
215
|
);
|
|
193
216
|
if (comments) {
|
|
194
217
|
for (const gc of comments) {
|
|
195
218
|
if (await getCommentByUpstreamId(gc.id)) continue;
|
|
219
|
+
if ((gc.body ?? "").includes("<!-- ework-mirror -->")) continue;
|
|
196
220
|
const upstreamNumber = upstreamIssueNumberFromComment(gc);
|
|
197
221
|
if (!upstreamNumber) continue;
|
|
198
222
|
const local = await getIssueByUpstreamNumber(this.project.id, upstreamNumber);
|
package/src/views/issueThread.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
listCachedModels,
|
|
18
18
|
type CommentRow,
|
|
19
19
|
type IssueWithMeta,
|
|
20
|
+
type ProjectRow,
|
|
20
21
|
} from "../store";
|
|
21
22
|
import { webUrlFromClone } from "./projectUpstreams";
|
|
22
23
|
|
|
@@ -37,6 +38,22 @@ export interface IssueThreadPayload {
|
|
|
37
38
|
comments: CommentView[];
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
// Issues in a project cloned from an upstream are that upstream's issues:
|
|
42
|
+
// #204 in a github-mirrored repo means github.com/o/r/issues/204. Normalize
|
|
43
|
+
// the configured remote (https/git@/ssh/git forms) to its web base; projects
|
|
44
|
+
// without an upstream keep resolving refs against this ework install.
|
|
45
|
+
export function upstreamRefBase(project: Pick<ProjectRow, "upstream_urls">): string | null {
|
|
46
|
+
const url = getDefaultUpstreamUrl(project);
|
|
47
|
+
if (!url) return null;
|
|
48
|
+
let m = url.match(/^git@([^:]+):(.+)$/);
|
|
49
|
+
if (m?.[1] && m[2]) return `https://${m[1]}/${m[2].replace(/\.git$/, "")}`;
|
|
50
|
+
m = url.match(/^(?:ssh|git):\/\/(?:git@)?([^\/]+)\/(.+)$/);
|
|
51
|
+
if (m?.[1] && m[2]) return `https://${m[1]}/${m[2].replace(/\.git$/, "")}`;
|
|
52
|
+
m = url.match(/^https?:\/\/([^\/]+)\/(.+)$/);
|
|
53
|
+
if (m?.[1] && m[2]) return `https://${m[1]}/${m[2].replace(/\.git$/, "")}`;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
function toView(c: CommentRow, issuePath = ""): CommentView {
|
|
41
58
|
return {
|
|
42
59
|
id: c.id,
|
|
@@ -105,7 +122,7 @@ export async function buildIssueThread(
|
|
|
105
122
|
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|
106
123
|
const currentPage = totalPages;
|
|
107
124
|
const { rows } = await listCommentsPage(issue.id, currentPage, PAGE_SIZE);
|
|
108
|
-
const issuePath = `/${owner}/${repo}/issues/${issue.number}`;
|
|
125
|
+
const issuePath = `${upstreamRefBase(project) ?? `/${owner}/${repo}`}/issues/${issue.number}`;
|
|
109
126
|
const views = await viewsFromComments(rows, issuePath);
|
|
110
127
|
const hasOlder = currentPage > 1;
|
|
111
128
|
const displayViews = orderForDisplay(views, cfg.commentSort);
|
|
@@ -188,7 +205,7 @@ export async function fetchIssuePage(
|
|
|
188
205
|
const issue = await getIssueWithMeta(project.id, number);
|
|
189
206
|
if (!issue) throw new StoreError(404, `#${number} 不存在`);
|
|
190
207
|
const { rows, page: clamped } = await listCommentsPage(issue.id, page, PAGE_SIZE);
|
|
191
|
-
const views = await viewsFromComments(rows, `/${owner}/${repo}/issues/${issue.number}`);
|
|
208
|
+
const views = await viewsFromComments(rows, `${upstreamRefBase(project) ?? `/${owner}/${repo}`}/issues/${issue.number}`);
|
|
192
209
|
return { issue, views, currentPage: clamped, hasOlder: clamped > 1 };
|
|
193
210
|
}
|
|
194
211
|
|
|
@@ -203,7 +220,7 @@ export async function fetchIssueSince(
|
|
|
203
220
|
const issue = await getIssueWithMeta(project.id, number);
|
|
204
221
|
if (!issue) throw new StoreError(404, `#${number} 不存在`);
|
|
205
222
|
const rows = await listCommentsSince(issue.id, sinceISO);
|
|
206
|
-
return await viewsFromComments(rows, `/${owner}/${repo}/issues/${issue.number}`);
|
|
223
|
+
return await viewsFromComments(rows, `${upstreamRefBase(project) ?? `/${owner}/${repo}`}/issues/${issue.number}`);
|
|
207
224
|
}
|
|
208
225
|
|
|
209
226
|
export function safeJsonEmbed(v: unknown): string {
|
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,
|