jowork 0.2.4 → 0.3.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/{chunk-ROIINI33.js → chunk-4PIT2GZ4.js} +13 -1
- package/dist/{chunk-XLYRHKG6.js → chunk-54SD5GBF.js} +1 -1
- package/dist/chunk-63AMINQC.js +156 -0
- package/dist/{chunk-XAEGXSEO.js → chunk-74AHY7X6.js} +4 -0
- package/dist/{chunk-7U3SXINY.js → chunk-ATAUWJYD.js} +320 -50
- package/dist/chunk-DQW74UCN.js +671 -0
- package/dist/chunk-EYP6WMFF.js +153 -0
- package/dist/{chunk-JSTXMDXI.js → chunk-FCFZCZHR.js} +1 -1
- package/dist/chunk-FX6Z3QHV.js +34 -0
- package/dist/chunk-HENAABEL.js +419 -0
- package/dist/chunk-OXWWOKC7.js +201 -0
- package/dist/chunk-QGHJ45PL.js +661 -0
- package/dist/chunk-RO3KK5RC.js +132 -0
- package/dist/{chunk-JE6TOU7W.js → chunk-TFMF3EXE.js} +2 -7
- package/dist/{chunk-TN327MDF.js → chunk-VX662YLA.js} +3 -3
- package/dist/cli.js +338 -149
- package/dist/{config-AI6UIJJN.js → config-FH2XLN7A.js} +2 -2
- package/dist/content-reader-VPGTR2SF.js +10 -0
- package/dist/context-ZNI3WOB7.js +10 -0
- package/dist/{credential-store-ZRZCSRPC.js → credential-store-OS5ZY4OW.js} +2 -2
- package/dist/{feishu-A6YVFKEN.js → feishu-XW5T6ER2.js} +8 -3
- package/dist/{git-manager-N35XSG4Y.js → git-manager-RVWV2GSV.js} +2 -1
- package/dist/github-PQKAYTLO.js +11 -0
- package/dist/{paths-JXOMBYIT.js → paths-FFRET6F7.js} +7 -3
- package/dist/{server-5GVWN2NB.js → server-WEADPUST.js} +59 -66
- package/dist/{setup-IDQDPCEJ.js → setup-S2S2CHB2.js} +91 -32
- package/dist/sync-SRLFR5NA.js +21 -0
- package/dist/transport.js +6 -4
- package/package.json +1 -1
- package/src/dashboard/public/app.js +34 -8
- package/src/dashboard/public/style.css +14 -0
- package/dist/chunk-AIXKXEYS.js +0 -547
- package/dist/chunk-L5ZR7TSK.js +0 -82
- package/dist/chunk-LS2AJM5A.js +0 -163
- package/dist/chunk-QMOFQX7X.js +0 -612
- package/dist/chunk-YJWTKFWX.js +0 -451
- package/dist/github-SHWUFNYB.js +0 -10
- package/dist/sync-7V54N62M.js +0 -18
package/dist/chunk-YJWTKFWX.js
DELETED
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
contentHash,
|
|
3
|
-
formatIssue,
|
|
4
|
-
formatPullRequest
|
|
5
|
-
} from "./chunk-QMOFQX7X.js";
|
|
6
|
-
import {
|
|
7
|
-
createId
|
|
8
|
-
} from "./chunk-JE6TOU7W.js";
|
|
9
|
-
import {
|
|
10
|
-
logError,
|
|
11
|
-
logInfo
|
|
12
|
-
} from "./chunk-MYDK7MWB.js";
|
|
13
|
-
|
|
14
|
-
// src/sync/linker.ts
|
|
15
|
-
var PATTERNS = [
|
|
16
|
-
// GitHub/GitLab PR/Issue references
|
|
17
|
-
{ type: "pr", regex: /(?:PR|pr|Pull Request|pull request)\s*#?(\d+)/g, confidence: "high" },
|
|
18
|
-
{ type: "issue", regex: /(?:issue|Issue|ISSUE)\s*#?(\d+)/g, confidence: "high" },
|
|
19
|
-
{ type: "issue", regex: /#(\d{2,6})\b/g, confidence: "medium" },
|
|
20
|
-
// bare #123
|
|
21
|
-
// Linear-style issue keys (e.g. LIN-234, PROJ-56)
|
|
22
|
-
// Requires 2+ digit number to reduce false positives (GPT-5, GLP-1 are NOT issues)
|
|
23
|
-
{ type: "issue", regex: /\b([A-Z]{2,10}-\d{2,6})\b/g, confidence: "high" },
|
|
24
|
-
// Git commit SHA
|
|
25
|
-
{ type: "commit", regex: /\b([0-9a-f]{7,40})\b/g, confidence: "low" },
|
|
26
|
-
// URLs
|
|
27
|
-
{ type: "url", regex: /https?:\/\/[^\s<>"{}|\\^`\[\]]+/g, confidence: "high" },
|
|
28
|
-
// @mentions (feishu user_id format)
|
|
29
|
-
{ type: "mention", regex: /@([a-zA-Z0-9_]+)/g, confidence: "medium" },
|
|
30
|
-
// Action items (Chinese + English)
|
|
31
|
-
{ type: "action_item", regex: /(?:需要|TODO|FIXME|待办|截止|deadline|action item|任务)[::\s]+([^\n。.]{5,80})/gi, confidence: "medium" }
|
|
32
|
-
];
|
|
33
|
-
function extractLinks(content) {
|
|
34
|
-
const links = [];
|
|
35
|
-
const seen = /* @__PURE__ */ new Set();
|
|
36
|
-
for (const pattern of PATTERNS) {
|
|
37
|
-
if (pattern.type === "commit" && content.length < 100) continue;
|
|
38
|
-
const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
|
|
39
|
-
let match;
|
|
40
|
-
while ((match = regex.exec(content)) !== null) {
|
|
41
|
-
const identifier = match[1] ?? match[0];
|
|
42
|
-
const key = `${pattern.type}:${identifier}`;
|
|
43
|
-
if (seen.has(key)) continue;
|
|
44
|
-
seen.add(key);
|
|
45
|
-
if (identifier.length < 3 && pattern.type !== "pr") continue;
|
|
46
|
-
if (pattern.type === "commit" && identifier.length < 7) continue;
|
|
47
|
-
let metadata;
|
|
48
|
-
if (pattern.type === "action_item") {
|
|
49
|
-
const surroundingText = content.slice(
|
|
50
|
-
Math.max(0, (match.index ?? 0) - 50),
|
|
51
|
-
Math.min(content.length, (match.index ?? 0) + match[0].length + 50)
|
|
52
|
-
);
|
|
53
|
-
const mentionMatch = surroundingText.match(/@([a-zA-Z0-9_]+)/);
|
|
54
|
-
const dateMatch = surroundingText.match(
|
|
55
|
-
/(\d{4}[-/]\d{1,2}[-/]\d{1,2}|\d{1,2}月\d{1,2}[日号]|\d{1,2}\/\d{1,2})/
|
|
56
|
-
);
|
|
57
|
-
if (mentionMatch || dateMatch) {
|
|
58
|
-
metadata = {};
|
|
59
|
-
if (mentionMatch) metadata.assignee = mentionMatch[1];
|
|
60
|
-
if (dateMatch) metadata.dueDate = dateMatch[1];
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
links.push({
|
|
64
|
-
linkType: pattern.type,
|
|
65
|
-
identifier,
|
|
66
|
-
confidence: pattern.confidence,
|
|
67
|
-
...metadata ? { metadata } : {}
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return links;
|
|
72
|
-
}
|
|
73
|
-
function processObjectLinks(sqlite, objectId, content) {
|
|
74
|
-
const links = extractLinks(content);
|
|
75
|
-
if (links.length === 0) return 0;
|
|
76
|
-
const insert = sqlite.prepare(`
|
|
77
|
-
INSERT OR IGNORE INTO object_links (id, source_object_id, target_object_id, link_type, identifier, metadata, confidence, created_at)
|
|
78
|
-
VALUES (?, ?, NULL, ?, ?, ?, ?, ?)
|
|
79
|
-
`);
|
|
80
|
-
const now = Date.now();
|
|
81
|
-
let count = 0;
|
|
82
|
-
const batch = sqlite.transaction(() => {
|
|
83
|
-
for (const link of links) {
|
|
84
|
-
const id = `${objectId}:${link.linkType}:${link.identifier}`.slice(0, 64);
|
|
85
|
-
insert.run(
|
|
86
|
-
id,
|
|
87
|
-
objectId,
|
|
88
|
-
link.linkType,
|
|
89
|
-
link.identifier,
|
|
90
|
-
link.metadata ? JSON.stringify(link.metadata) : null,
|
|
91
|
-
link.confidence,
|
|
92
|
-
now
|
|
93
|
-
);
|
|
94
|
-
count++;
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
batch();
|
|
98
|
-
return count;
|
|
99
|
-
}
|
|
100
|
-
function linkAllUnprocessed(sqlite) {
|
|
101
|
-
const unprocessed = sqlite.prepare(`
|
|
102
|
-
SELECT o.id, ob.content FROM objects o
|
|
103
|
-
JOIN object_bodies ob ON ob.object_id = o.id
|
|
104
|
-
WHERE o.links_processed = 0
|
|
105
|
-
LIMIT 1000
|
|
106
|
-
`).all();
|
|
107
|
-
if (unprocessed.length === 0) return { processed: 0, linksCreated: 0 };
|
|
108
|
-
let linksCreated = 0;
|
|
109
|
-
const markProcessed = sqlite.prepare("UPDATE objects SET links_processed = 1 WHERE id = ?");
|
|
110
|
-
const batch = sqlite.transaction(() => {
|
|
111
|
-
for (const obj of unprocessed) {
|
|
112
|
-
linksCreated += processObjectLinks(sqlite, obj.id, obj.content);
|
|
113
|
-
markProcessed.run(obj.id);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
batch();
|
|
117
|
-
logInfo("linker", `Processed ${unprocessed.length} objects, created ${linksCreated} links`);
|
|
118
|
-
return { processed: unprocessed.length, linksCreated };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/sync/gitlab.ts
|
|
122
|
-
var defaultLogger = {
|
|
123
|
-
info: (msg, ctx) => logInfo("sync", msg, ctx),
|
|
124
|
-
warn: (msg, ctx) => logError("sync", msg, ctx),
|
|
125
|
-
error: (msg, ctx) => logError("sync", msg, ctx)
|
|
126
|
-
};
|
|
127
|
-
var MAX_PROJECTS = 20;
|
|
128
|
-
var RATE_LIMIT_DELAY_MS = 200;
|
|
129
|
-
async function syncGitLab(sqlite, data, logger = defaultLogger, fileWriter) {
|
|
130
|
-
const token = data.token;
|
|
131
|
-
if (!token) throw new Error("Missing GitLab token");
|
|
132
|
-
const baseUrl = data.apiUrl || "https://gitlab.com";
|
|
133
|
-
const headers = {
|
|
134
|
-
"PRIVATE-TOKEN": token
|
|
135
|
-
};
|
|
136
|
-
let projects = 0;
|
|
137
|
-
let issues = 0;
|
|
138
|
-
let mrs = 0;
|
|
139
|
-
let newObjects = 0;
|
|
140
|
-
const projectsRes = await fetch(
|
|
141
|
-
`${baseUrl}/api/v4/projects?membership=true&per_page=20&order_by=last_activity_at`,
|
|
142
|
-
{ headers }
|
|
143
|
-
);
|
|
144
|
-
if (!projectsRes.ok) {
|
|
145
|
-
if (projectsRes.status === 401) throw new Error("GitLab token expired or invalid");
|
|
146
|
-
throw new Error(`GitLab API error: ${projectsRes.status}`);
|
|
147
|
-
}
|
|
148
|
-
const projectList = await projectsRes.json();
|
|
149
|
-
projects = projectList.length;
|
|
150
|
-
logger.info(`Found ${projects} GitLab projects`);
|
|
151
|
-
const checkExists = sqlite.prepare("SELECT id FROM objects WHERE uri = ?");
|
|
152
|
-
const insertObj = sqlite.prepare(`
|
|
153
|
-
INSERT INTO objects (id, source, source_type, uri, title, summary, tags, content_hash, last_synced_at, created_at)
|
|
154
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
155
|
-
`);
|
|
156
|
-
const insertBody = sqlite.prepare(`
|
|
157
|
-
INSERT OR REPLACE INTO object_bodies (object_id, content, content_type, fetched_at)
|
|
158
|
-
VALUES (?, ?, ?, ?)
|
|
159
|
-
`);
|
|
160
|
-
const insertFts = sqlite.prepare(`
|
|
161
|
-
INSERT INTO objects_fts(rowid, title, summary, tags, source, source_type, body_excerpt)
|
|
162
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
163
|
-
`);
|
|
164
|
-
const getRowid = sqlite.prepare("SELECT rowid FROM objects WHERE id = ?");
|
|
165
|
-
for (const project of projectList.slice(0, MAX_PROJECTS)) {
|
|
166
|
-
const encodedPath = encodeURIComponent(project.path_with_namespace);
|
|
167
|
-
try {
|
|
168
|
-
const issuesRes = await fetch(
|
|
169
|
-
`${baseUrl}/api/v4/projects/${encodedPath}/issues?state=all&per_page=30&order_by=updated_at`,
|
|
170
|
-
{ headers }
|
|
171
|
-
);
|
|
172
|
-
if (issuesRes.ok) {
|
|
173
|
-
const issueList = await issuesRes.json();
|
|
174
|
-
const batchInsert = sqlite.transaction((items) => {
|
|
175
|
-
for (const item of items) {
|
|
176
|
-
const uri = `gitlab://${project.path_with_namespace}/issue/${item.iid}`;
|
|
177
|
-
if (checkExists.get(uri)) continue;
|
|
178
|
-
const now = Date.now();
|
|
179
|
-
const id = createId("obj");
|
|
180
|
-
const title = `${project.path_with_namespace}#${item.iid}: ${item.title}`;
|
|
181
|
-
const summary = item.description ? item.description.length > 200 ? item.description.slice(0, 200) + "..." : item.description : item.title;
|
|
182
|
-
const tags = JSON.stringify(["gitlab", "issue", item.state, ...item.labels]);
|
|
183
|
-
const body = formatGitLabIssueBody(item, project.path_with_namespace);
|
|
184
|
-
const hash = contentHash(title + (item.description ?? ""));
|
|
185
|
-
insertObj.run(id, "gitlab", "issue", uri, title, summary, tags, hash, now, new Date(item.created_at).getTime());
|
|
186
|
-
insertBody.run(id, body, "text/plain", now);
|
|
187
|
-
try {
|
|
188
|
-
const rowid = getRowid.get(id);
|
|
189
|
-
if (rowid) {
|
|
190
|
-
const excerpt = body.length > 500 ? body.slice(0, 500) : body;
|
|
191
|
-
insertFts.run(rowid.rowid, title, summary ?? "", tags, "gitlab", "issue", excerpt);
|
|
192
|
-
}
|
|
193
|
-
} catch {
|
|
194
|
-
}
|
|
195
|
-
if (fileWriter) {
|
|
196
|
-
try {
|
|
197
|
-
const fileContent = formatIssue({
|
|
198
|
-
source: "gitlab",
|
|
199
|
-
repo: project.path_with_namespace,
|
|
200
|
-
number: item.iid,
|
|
201
|
-
title: item.title,
|
|
202
|
-
state: item.state,
|
|
203
|
-
author: item.author?.username ?? "unknown",
|
|
204
|
-
labels: item.labels,
|
|
205
|
-
created: item.created_at,
|
|
206
|
-
uri,
|
|
207
|
-
body: item.description ?? ""
|
|
208
|
-
});
|
|
209
|
-
const filePath = fileWriter.writeObject("gitlab", "issue", {
|
|
210
|
-
id,
|
|
211
|
-
repo: project.path_with_namespace,
|
|
212
|
-
number: item.iid,
|
|
213
|
-
title: item.title
|
|
214
|
-
}, fileContent);
|
|
215
|
-
sqlite.prepare("UPDATE objects SET file_path = ? WHERE id = ?").run(filePath, id);
|
|
216
|
-
} catch {
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
newObjects++;
|
|
220
|
-
issues++;
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
batchInsert(issueList);
|
|
224
|
-
} else {
|
|
225
|
-
logger.warn(`Failed to fetch issues for ${project.path_with_namespace}: ${issuesRes.status}`);
|
|
226
|
-
}
|
|
227
|
-
} catch (err) {
|
|
228
|
-
logger.warn(`Error fetching issues for ${project.path_with_namespace}: ${err}`);
|
|
229
|
-
}
|
|
230
|
-
try {
|
|
231
|
-
const mrsRes = await fetch(
|
|
232
|
-
`${baseUrl}/api/v4/projects/${encodedPath}/merge_requests?state=all&per_page=30&order_by=updated_at`,
|
|
233
|
-
{ headers }
|
|
234
|
-
);
|
|
235
|
-
if (mrsRes.ok) {
|
|
236
|
-
const mrList = await mrsRes.json();
|
|
237
|
-
const batchInsert = sqlite.transaction((items) => {
|
|
238
|
-
for (const item of items) {
|
|
239
|
-
const uri = `gitlab://${project.path_with_namespace}/merge_request/${item.iid}`;
|
|
240
|
-
if (checkExists.get(uri)) continue;
|
|
241
|
-
const now = Date.now();
|
|
242
|
-
const id = createId("obj");
|
|
243
|
-
const title = `${project.path_with_namespace}!${item.iid}: ${item.title}`;
|
|
244
|
-
const summary = item.description ? item.description.length > 200 ? item.description.slice(0, 200) + "..." : item.description : item.title;
|
|
245
|
-
const tags = JSON.stringify(["gitlab", "merge_request", item.state, ...item.labels]);
|
|
246
|
-
const body = formatGitLabMRBody(item, project.path_with_namespace);
|
|
247
|
-
const hash = contentHash(title + (item.description ?? ""));
|
|
248
|
-
insertObj.run(id, "gitlab", "merge_request", uri, title, summary, tags, hash, now, new Date(item.created_at).getTime());
|
|
249
|
-
insertBody.run(id, body, "text/plain", now);
|
|
250
|
-
try {
|
|
251
|
-
const rowid = getRowid.get(id);
|
|
252
|
-
if (rowid) {
|
|
253
|
-
const excerpt = body.length > 500 ? body.slice(0, 500) : body;
|
|
254
|
-
insertFts.run(rowid.rowid, title, summary ?? "", tags, "gitlab", "merge_request", excerpt);
|
|
255
|
-
}
|
|
256
|
-
} catch {
|
|
257
|
-
}
|
|
258
|
-
if (fileWriter) {
|
|
259
|
-
try {
|
|
260
|
-
const fileContent = formatPullRequest({
|
|
261
|
-
source: "gitlab",
|
|
262
|
-
repo: project.path_with_namespace,
|
|
263
|
-
number: item.iid,
|
|
264
|
-
title: item.title,
|
|
265
|
-
state: item.state,
|
|
266
|
-
author: item.author?.username ?? "unknown",
|
|
267
|
-
labels: item.labels,
|
|
268
|
-
created: item.created_at,
|
|
269
|
-
uri,
|
|
270
|
-
body: item.description ?? "",
|
|
271
|
-
sourceBranch: item.source_branch,
|
|
272
|
-
targetBranch: item.target_branch
|
|
273
|
-
});
|
|
274
|
-
const filePath = fileWriter.writeObject("gitlab", "merge_request", {
|
|
275
|
-
id,
|
|
276
|
-
repo: project.path_with_namespace,
|
|
277
|
-
number: item.iid,
|
|
278
|
-
title: item.title
|
|
279
|
-
}, fileContent);
|
|
280
|
-
sqlite.prepare("UPDATE objects SET file_path = ? WHERE id = ?").run(filePath, id);
|
|
281
|
-
} catch {
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
newObjects++;
|
|
285
|
-
mrs++;
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
batchInsert(mrList);
|
|
289
|
-
} else {
|
|
290
|
-
logger.warn(`Failed to fetch MRs for ${project.path_with_namespace}: ${mrsRes.status}`);
|
|
291
|
-
}
|
|
292
|
-
} catch (err) {
|
|
293
|
-
logger.warn(`Error fetching MRs for ${project.path_with_namespace}: ${err}`);
|
|
294
|
-
}
|
|
295
|
-
await new Promise((r) => setTimeout(r, RATE_LIMIT_DELAY_MS));
|
|
296
|
-
}
|
|
297
|
-
logger.info("GitLab sync complete", { projects, issues, mrs, newObjects });
|
|
298
|
-
return { projects, issues, mrs, newObjects };
|
|
299
|
-
}
|
|
300
|
-
function formatGitLabIssueBody(item, project) {
|
|
301
|
-
return [
|
|
302
|
-
`${project}#${item.iid}: ${item.title}`,
|
|
303
|
-
`State: ${item.state} | Author: ${item.author?.username ?? "unknown"} | Created: ${item.created_at}`,
|
|
304
|
-
`Labels: ${item.labels.join(", ") || "none"}`,
|
|
305
|
-
"",
|
|
306
|
-
item.description ?? "(no description)"
|
|
307
|
-
].join("\n");
|
|
308
|
-
}
|
|
309
|
-
function formatGitLabMRBody(item, project) {
|
|
310
|
-
return [
|
|
311
|
-
`${project}!${item.iid}: ${item.title}`,
|
|
312
|
-
`State: ${item.state} | Author: ${item.author?.username ?? "unknown"} | Created: ${item.created_at}`,
|
|
313
|
-
`Branch: ${item.source_branch} \u2192 ${item.target_branch}`,
|
|
314
|
-
`Labels: ${item.labels.join(", ") || "none"}`,
|
|
315
|
-
"",
|
|
316
|
-
item.description ?? "(no description)"
|
|
317
|
-
].join("\n");
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// src/sync/linear.ts
|
|
321
|
-
var defaultLogger2 = {
|
|
322
|
-
info: (msg, ctx) => logInfo("sync", msg, ctx),
|
|
323
|
-
warn: (msg, ctx) => logError("sync", msg, ctx),
|
|
324
|
-
error: (msg, ctx) => logError("sync", msg, ctx)
|
|
325
|
-
};
|
|
326
|
-
var LINEAR_API = "https://api.linear.app/graphql";
|
|
327
|
-
var ISSUES_QUERY = `
|
|
328
|
-
query {
|
|
329
|
-
issues(first: 50, orderBy: updatedAt) {
|
|
330
|
-
nodes {
|
|
331
|
-
id
|
|
332
|
-
identifier
|
|
333
|
-
title
|
|
334
|
-
description
|
|
335
|
-
url
|
|
336
|
-
state { name }
|
|
337
|
-
assignee { name }
|
|
338
|
-
labels { nodes { name } }
|
|
339
|
-
createdAt
|
|
340
|
-
updatedAt
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
`;
|
|
345
|
-
async function syncLinear(sqlite, data, logger = defaultLogger2, fileWriter) {
|
|
346
|
-
const apiKey = data.apiKey;
|
|
347
|
-
if (!apiKey) throw new Error("Missing Linear API key");
|
|
348
|
-
const headers = {
|
|
349
|
-
"Content-Type": "application/json",
|
|
350
|
-
Authorization: apiKey
|
|
351
|
-
};
|
|
352
|
-
let issues = 0;
|
|
353
|
-
let newObjects = 0;
|
|
354
|
-
const res = await fetch(LINEAR_API, {
|
|
355
|
-
method: "POST",
|
|
356
|
-
headers,
|
|
357
|
-
body: JSON.stringify({ query: ISSUES_QUERY })
|
|
358
|
-
});
|
|
359
|
-
if (!res.ok) {
|
|
360
|
-
if (res.status === 401) throw new Error("Linear API key expired or invalid");
|
|
361
|
-
throw new Error(`Linear API error: ${res.status}`);
|
|
362
|
-
}
|
|
363
|
-
const body = await res.json();
|
|
364
|
-
if (body.errors?.length) {
|
|
365
|
-
throw new Error(`Linear GraphQL error: ${body.errors[0].message}`);
|
|
366
|
-
}
|
|
367
|
-
const issueList = body.data?.issues?.nodes ?? [];
|
|
368
|
-
issues = issueList.length;
|
|
369
|
-
logger.info(`Found ${issues} Linear issues`);
|
|
370
|
-
const checkExists = sqlite.prepare("SELECT id FROM objects WHERE uri = ?");
|
|
371
|
-
const insertObj = sqlite.prepare(`
|
|
372
|
-
INSERT INTO objects (id, source, source_type, uri, title, summary, tags, content_hash, last_synced_at, created_at)
|
|
373
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
374
|
-
`);
|
|
375
|
-
const insertBody = sqlite.prepare(`
|
|
376
|
-
INSERT OR REPLACE INTO object_bodies (object_id, content, content_type, fetched_at)
|
|
377
|
-
VALUES (?, ?, ?, ?)
|
|
378
|
-
`);
|
|
379
|
-
const insertFts = sqlite.prepare(`
|
|
380
|
-
INSERT INTO objects_fts(rowid, title, summary, tags, source, source_type, body_excerpt)
|
|
381
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
382
|
-
`);
|
|
383
|
-
const getRowid = sqlite.prepare("SELECT rowid FROM objects WHERE id = ?");
|
|
384
|
-
const batchInsert = sqlite.transaction((items) => {
|
|
385
|
-
for (const item of items) {
|
|
386
|
-
const uri = `linear://${item.identifier}`;
|
|
387
|
-
if (checkExists.get(uri)) continue;
|
|
388
|
-
const now = Date.now();
|
|
389
|
-
const id = createId("obj");
|
|
390
|
-
const title = `${item.identifier}: ${item.title}`;
|
|
391
|
-
const summary = item.description ? item.description.length > 200 ? item.description.slice(0, 200) + "..." : item.description : item.title;
|
|
392
|
-
const labelNames = item.labels.nodes.map((l) => l.name);
|
|
393
|
-
const tags = JSON.stringify(["linear", "issue", item.state.name, ...labelNames]);
|
|
394
|
-
const bodyText = formatLinearIssueBody(item);
|
|
395
|
-
const hash = contentHash(title + (item.description ?? ""));
|
|
396
|
-
insertObj.run(id, "linear", "issue", uri, title, summary, tags, hash, now, new Date(item.createdAt).getTime());
|
|
397
|
-
insertBody.run(id, bodyText, "text/plain", now);
|
|
398
|
-
try {
|
|
399
|
-
const rowid = getRowid.get(id);
|
|
400
|
-
if (rowid) {
|
|
401
|
-
const excerpt = bodyText.length > 500 ? bodyText.slice(0, 500) : bodyText;
|
|
402
|
-
insertFts.run(rowid.rowid, title, summary ?? "", tags, "linear", "issue", excerpt);
|
|
403
|
-
}
|
|
404
|
-
} catch {
|
|
405
|
-
}
|
|
406
|
-
if (fileWriter) {
|
|
407
|
-
try {
|
|
408
|
-
const labelNames2 = item.labels.nodes.map((l) => l.name);
|
|
409
|
-
const fileContent = formatIssue({
|
|
410
|
-
source: "linear",
|
|
411
|
-
repo: item.identifier.split("-")[0] ?? "linear",
|
|
412
|
-
number: parseInt(item.identifier.split("-")[1] ?? "0"),
|
|
413
|
-
title: item.title,
|
|
414
|
-
state: item.state.name,
|
|
415
|
-
author: item.assignee?.name ?? "unassigned",
|
|
416
|
-
labels: labelNames2,
|
|
417
|
-
created: item.createdAt,
|
|
418
|
-
uri: `linear://${item.identifier}`,
|
|
419
|
-
body: item.description ?? ""
|
|
420
|
-
});
|
|
421
|
-
const filePath = fileWriter.writeObject("linear", "issue", {
|
|
422
|
-
id,
|
|
423
|
-
identifier: item.identifier,
|
|
424
|
-
title: item.title
|
|
425
|
-
}, fileContent);
|
|
426
|
-
sqlite.prepare("UPDATE objects SET file_path = ? WHERE id = ?").run(filePath, id);
|
|
427
|
-
} catch {
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
newObjects++;
|
|
431
|
-
}
|
|
432
|
-
});
|
|
433
|
-
batchInsert(issueList);
|
|
434
|
-
logger.info("Linear sync complete", { issues, newObjects });
|
|
435
|
-
return { issues, newObjects };
|
|
436
|
-
}
|
|
437
|
-
function formatLinearIssueBody(item) {
|
|
438
|
-
return [
|
|
439
|
-
`${item.identifier}: ${item.title}`,
|
|
440
|
-
`State: ${item.state.name} | Assignee: ${item.assignee?.name ?? "unassigned"} | Created: ${item.createdAt}`,
|
|
441
|
-
`Labels: ${item.labels.nodes.map((l) => l.name).join(", ") || "none"}`,
|
|
442
|
-
"",
|
|
443
|
-
item.description ?? "(no description)"
|
|
444
|
-
].join("\n");
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
export {
|
|
448
|
-
linkAllUnprocessed,
|
|
449
|
-
syncGitLab,
|
|
450
|
-
syncLinear
|
|
451
|
-
};
|
package/dist/github-SHWUFNYB.js
DELETED
package/dist/sync-7V54N62M.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
runSync,
|
|
3
|
-
syncCommand
|
|
4
|
-
} from "./chunk-AIXKXEYS.js";
|
|
5
|
-
import "./chunk-YJWTKFWX.js";
|
|
6
|
-
import "./chunk-XAEGXSEO.js";
|
|
7
|
-
import "./chunk-XLYRHKG6.js";
|
|
8
|
-
import "./chunk-ROIINI33.js";
|
|
9
|
-
import "./chunk-LS2AJM5A.js";
|
|
10
|
-
import "./chunk-QMOFQX7X.js";
|
|
11
|
-
import "./chunk-JE6TOU7W.js";
|
|
12
|
-
import "./chunk-L5ZR7TSK.js";
|
|
13
|
-
import "./chunk-MYDK7MWB.js";
|
|
14
|
-
import "./chunk-UJ4KEHGZ.js";
|
|
15
|
-
export {
|
|
16
|
-
runSync,
|
|
17
|
-
syncCommand
|
|
18
|
-
};
|