ework-web 0.10.96 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.96",
3
+ "version": "0.10.97",
4
4
  "type": "module",
5
5
  "description": "ework-web — standalone multi-project issue tracker. Local SQLite-backed, no external API dependency. Bun + TypeScript + SSR HTML.",
6
6
  "license": "MIT",
package/src/store.ts CHANGED
@@ -76,6 +76,7 @@ export interface IssueRow {
76
76
  ai_status: string;
77
77
  model: string;
78
78
  runtime: string;
79
+ upstream_issue_number: number | null;
79
80
  }
80
81
 
81
82
  export interface IssueWithMeta extends IssueRow {
@@ -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
- return `${this.sync.base_url}/api/v1/repos/${this.sync.upstream_owner}/${this.sync.upstream_repo}${path}`;
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[]>(`/issues/${gi.number}/comments?limit=50&order=asc`);
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
- `/issues?state=open&type=issues&limit=50&page=${page}&sort=created&order=asc`
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
- `/issues?state=all&type=issues&limit=30&sort=updated&order=desc`
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
- `/issues/comments?limit=50&sort=updated&order=asc${
190
- this.sync.comment_cursor ? `&since=${encodeURIComponent(this.sync.comment_cursor)}` : ""
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/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,