standup-mr 0.1.2 → 0.2.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/CHANGELOG.md +77 -0
- package/README.md +112 -23
- package/dist/chunk-MIQ7FQS6.js +1108 -0
- package/dist/cli.js +10 -13
- package/dist/index.d.ts +138 -17
- package/dist/index.js +45 -5
- package/dist/mcp/server.js +598 -81
- package/mcp/README.md +28 -6
- package/package.json +4 -3
- package/skills/standup/SKILL.md +73 -26
- package/dist/chunk-SKCJT2AY.js +0 -551
package/dist/chunk-SKCJT2AY.js
DELETED
|
@@ -1,551 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/buckets/buckets.constants.ts
|
|
4
|
-
var STALE_DAYS = 14;
|
|
5
|
-
|
|
6
|
-
// src/dates/dates.constants.ts
|
|
7
|
-
var DAYS = {
|
|
8
|
-
en: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
|
9
|
-
tr: ["Pazar", "Pazartesi", "Sal\u0131", "\xC7ar\u015Famba", "Per\u015Fembe", "Cuma", "Cumartesi"]
|
|
10
|
-
};
|
|
11
|
-
var MONTHS = {
|
|
12
|
-
en: [
|
|
13
|
-
"January",
|
|
14
|
-
"February",
|
|
15
|
-
"March",
|
|
16
|
-
"April",
|
|
17
|
-
"May",
|
|
18
|
-
"June",
|
|
19
|
-
"July",
|
|
20
|
-
"August",
|
|
21
|
-
"September",
|
|
22
|
-
"October",
|
|
23
|
-
"November",
|
|
24
|
-
"December"
|
|
25
|
-
],
|
|
26
|
-
tr: [
|
|
27
|
-
"Ocak",
|
|
28
|
-
"\u015Eubat",
|
|
29
|
-
"Mart",
|
|
30
|
-
"Nisan",
|
|
31
|
-
"May\u0131s",
|
|
32
|
-
"Haziran",
|
|
33
|
-
"Temmuz",
|
|
34
|
-
"A\u011Fustos",
|
|
35
|
-
"Eyl\xFCl",
|
|
36
|
-
"Ekim",
|
|
37
|
-
"Kas\u0131m",
|
|
38
|
-
"Aral\u0131k"
|
|
39
|
-
]
|
|
40
|
-
};
|
|
41
|
-
var MS_PER_DAY = 864e5;
|
|
42
|
-
|
|
43
|
-
// src/dates/dates.ts
|
|
44
|
-
function isoDay(day) {
|
|
45
|
-
const year = day.getFullYear();
|
|
46
|
-
const month = String(day.getMonth() + 1).padStart(2, "0");
|
|
47
|
-
const date = String(day.getDate()).padStart(2, "0");
|
|
48
|
-
return `${year}-${month}-${date}`;
|
|
49
|
-
}
|
|
50
|
-
function label(day, lang = "en") {
|
|
51
|
-
const key = DAYS[lang] ? lang : "en";
|
|
52
|
-
const weekday = DAYS[key][day.getDay()];
|
|
53
|
-
const month = MONTHS[key][day.getMonth()];
|
|
54
|
-
const date = day.getDate();
|
|
55
|
-
return key === "tr" ? `${date} ${month} ${weekday}` : `${weekday}, ${date} ${month}`;
|
|
56
|
-
}
|
|
57
|
-
function previousActiveDay(eventDates, today) {
|
|
58
|
-
const cutoff = isoDay(today);
|
|
59
|
-
const past = [...eventDates].filter((d) => d < cutoff).sort();
|
|
60
|
-
const latest = past[past.length - 1];
|
|
61
|
-
if (!latest) return { date: null, gapDays: null };
|
|
62
|
-
const gapDays = Math.round(
|
|
63
|
-
(Date.parse(`${cutoff}T00:00:00Z`) - Date.parse(`${latest}T00:00:00Z`)) / MS_PER_DAY
|
|
64
|
-
);
|
|
65
|
-
return { date: latest, gapDays };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// src/buckets/buckets.ts
|
|
69
|
-
function classify(mr, today, staleDays = STALE_DAYS) {
|
|
70
|
-
if (mr.draft) return "draft";
|
|
71
|
-
if (mr.pipeline === "failed" || mr.unresolved > 0) return "blocked";
|
|
72
|
-
const age = Math.round(
|
|
73
|
-
(Date.parse(`${isoDay(today)}T00:00:00Z`) - Date.parse(`${mr.updated}T00:00:00Z`)) / MS_PER_DAY
|
|
74
|
-
);
|
|
75
|
-
return age >= staleDays ? "stale" : "ready";
|
|
76
|
-
}
|
|
77
|
-
function markMissingPipelines(mrs) {
|
|
78
|
-
const withCi = new Set(mrs.filter((mr) => mr.pipeline).map((mr) => mr.project));
|
|
79
|
-
for (const mr of mrs) {
|
|
80
|
-
mr.pipelineMissing = mr.pipeline === null && withCi.has(mr.project);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/config/config.ts
|
|
85
|
-
import { execFileSync } from "child_process";
|
|
86
|
-
|
|
87
|
-
// src/config/config.constants.ts
|
|
88
|
-
var GLAB_TIMEOUT_MS = 15e3;
|
|
89
|
-
|
|
90
|
-
// src/config/config.ts
|
|
91
|
-
var ConfigError = class extends Error {
|
|
92
|
-
constructor(message) {
|
|
93
|
-
super(message);
|
|
94
|
-
this.name = "ConfigError";
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
function resolveHost(cliHost, envHost, glabHostList) {
|
|
98
|
-
if (cliHost) return cliHost;
|
|
99
|
-
if (envHost) return envHost;
|
|
100
|
-
const hosts = glabHostList ?? [];
|
|
101
|
-
if (hosts.length === 1) return hosts[0];
|
|
102
|
-
if (hosts.length === 0) {
|
|
103
|
-
throw new ConfigError(
|
|
104
|
-
"No GitLab host configured. Pass --host, set GITLAB_HOST, or log in with `glab auth login`."
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
throw new ConfigError(
|
|
108
|
-
`Multiple GitLab hosts found (${[...hosts].sort().join(", ")}). Pick one with --host or GITLAB_HOST.`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
function resolveToken(host, cliToken, envToken, glabLookup) {
|
|
112
|
-
if (cliToken) return cliToken;
|
|
113
|
-
if (envToken) return envToken;
|
|
114
|
-
if (glabLookup) {
|
|
115
|
-
const token = glabLookup(host);
|
|
116
|
-
if (token) return token;
|
|
117
|
-
}
|
|
118
|
-
throw new ConfigError(
|
|
119
|
-
`No token for ${host}. Pass --token, set GITLAB_TOKEN, or run \`glab auth login --hostname ${host}\`.`
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
function glab(args) {
|
|
123
|
-
try {
|
|
124
|
-
return execFileSync("glab", args, {
|
|
125
|
-
encoding: "utf8",
|
|
126
|
-
timeout: GLAB_TIMEOUT_MS,
|
|
127
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
128
|
-
}).trim();
|
|
129
|
-
} catch {
|
|
130
|
-
return "";
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function parseGlabHosts(statusOutput) {
|
|
134
|
-
const hosts = [...statusOutput.matchAll(/Logged in to (\S+)/g)].map((match) => match[1]);
|
|
135
|
-
return [...new Set(hosts)].sort();
|
|
136
|
-
}
|
|
137
|
-
function glabStatus() {
|
|
138
|
-
try {
|
|
139
|
-
return execFileSync("glab", ["auth", "status"], {
|
|
140
|
-
encoding: "utf8",
|
|
141
|
-
timeout: GLAB_TIMEOUT_MS,
|
|
142
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
143
|
-
});
|
|
144
|
-
} catch (error) {
|
|
145
|
-
const result = error;
|
|
146
|
-
return `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function glabHosts() {
|
|
150
|
-
return parseGlabHosts(glabStatus());
|
|
151
|
-
}
|
|
152
|
-
function glabToken(host) {
|
|
153
|
-
return glab(["config", "get", "token", "--host", host]);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// src/notify/notify.constants.ts
|
|
157
|
-
var PAYLOAD_FIELD = { slack: "text", discord: "content" };
|
|
158
|
-
|
|
159
|
-
// src/notify/notify.ts
|
|
160
|
-
async function postWebhook(url, text, kind = "slack", fetchImpl = fetch) {
|
|
161
|
-
const field = PAYLOAD_FIELD[kind];
|
|
162
|
-
if (!field) {
|
|
163
|
-
throw new Error(
|
|
164
|
-
`Unknown webhook kind "${kind}". Use one of: ${Object.keys(PAYLOAD_FIELD).join(", ")}.`
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
const response = await fetchImpl(url, {
|
|
168
|
-
method: "POST",
|
|
169
|
-
headers: { "Content-Type": "application/json" },
|
|
170
|
-
body: JSON.stringify({ [field]: text })
|
|
171
|
-
});
|
|
172
|
-
if (!response.ok) {
|
|
173
|
-
throw new Error(`Webhook rejected the message: HTTP ${response.status}.`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// src/trace/trace.constants.ts
|
|
178
|
-
var ANSI = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
179
|
-
var SECTION = /section_(start|end):\d+:\S*/g;
|
|
180
|
-
var SIGNAL = /(\berror\b|Error:|fatal:|npm ERR!|\bfailed\b)/i;
|
|
181
|
-
var NOISE = /^(Cleaning up|Job failed: exit status|ERROR: Job failed|Uploading artifacts|Job succeeded)/i;
|
|
182
|
-
var MAX_LINE = 200;
|
|
183
|
-
|
|
184
|
-
// src/trace/trace.ts
|
|
185
|
-
function extractErrors(rawTrace, limit = 8) {
|
|
186
|
-
if (!rawTrace) return [];
|
|
187
|
-
const cleaned = rawTrace.replace(ANSI, "").replace(SECTION, "");
|
|
188
|
-
const seen = /* @__PURE__ */ new Set();
|
|
189
|
-
const hits = [];
|
|
190
|
-
for (const rawLine of cleaned.split("\n")) {
|
|
191
|
-
const line = rawLine.trim();
|
|
192
|
-
if (!line || NOISE.test(line) || !SIGNAL.test(line)) continue;
|
|
193
|
-
const clipped = line.slice(0, MAX_LINE);
|
|
194
|
-
if (seen.has(clipped)) continue;
|
|
195
|
-
seen.add(clipped);
|
|
196
|
-
hits.push(clipped);
|
|
197
|
-
}
|
|
198
|
-
return hits.slice(-limit);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// src/providers/gitlab/gitlab.constants.ts
|
|
202
|
-
var PAGE_SIZE = 100;
|
|
203
|
-
var FRESH_REVIEW_DAYS = 7;
|
|
204
|
-
|
|
205
|
-
// src/providers/gitlab/gitlab.ts
|
|
206
|
-
var GitLabProvider = class {
|
|
207
|
-
host;
|
|
208
|
-
api;
|
|
209
|
-
token;
|
|
210
|
-
fetchImpl;
|
|
211
|
-
constructor(host, token, fetchImpl = fetch) {
|
|
212
|
-
this.host = host;
|
|
213
|
-
this.token = token;
|
|
214
|
-
this.api = `https://${host}/api/v4`;
|
|
215
|
-
this.fetchImpl = fetchImpl;
|
|
216
|
-
}
|
|
217
|
-
url(path, params) {
|
|
218
|
-
const base = `${this.api}/${path}`;
|
|
219
|
-
if (!params || Object.keys(params).length === 0) return base;
|
|
220
|
-
const query = new URLSearchParams();
|
|
221
|
-
for (const [key, value] of Object.entries(params)) {
|
|
222
|
-
query.set(key, String(value));
|
|
223
|
-
}
|
|
224
|
-
return `${base}?${query.toString()}`;
|
|
225
|
-
}
|
|
226
|
-
async getJson(path, params) {
|
|
227
|
-
try {
|
|
228
|
-
const response = await this.fetchImpl(this.url(path, params), {
|
|
229
|
-
headers: { "PRIVATE-TOKEN": this.token }
|
|
230
|
-
});
|
|
231
|
-
if (!response.ok) return null;
|
|
232
|
-
return await response.json();
|
|
233
|
-
} catch {
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
async getText(path) {
|
|
238
|
-
try {
|
|
239
|
-
const response = await this.fetchImpl(this.url(path), {
|
|
240
|
-
headers: { "PRIVATE-TOKEN": this.token }
|
|
241
|
-
});
|
|
242
|
-
if (!response.ok) return "";
|
|
243
|
-
return await response.text();
|
|
244
|
-
} catch {
|
|
245
|
-
return "";
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
async getPaged(path, params = {}, cap = 5) {
|
|
249
|
-
const rows = [];
|
|
250
|
-
for (let page = 1; page <= cap; page += 1) {
|
|
251
|
-
const chunk = await this.getJson(path, {
|
|
252
|
-
...params,
|
|
253
|
-
per_page: PAGE_SIZE,
|
|
254
|
-
page
|
|
255
|
-
});
|
|
256
|
-
if (!chunk || chunk.length === 0) break;
|
|
257
|
-
rows.push(...chunk);
|
|
258
|
-
if (chunk.length < PAGE_SIZE) break;
|
|
259
|
-
}
|
|
260
|
-
return rows;
|
|
261
|
-
}
|
|
262
|
-
async getIdentity() {
|
|
263
|
-
const me = await this.getJson("user");
|
|
264
|
-
if (!me) {
|
|
265
|
-
throw new Error(
|
|
266
|
-
`Could not reach ${this.host}. Check the host, the token, and network access.`
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
return { id: me.id, username: me.username };
|
|
270
|
-
}
|
|
271
|
-
async projectPath(projectId) {
|
|
272
|
-
const project = await this.getJson(
|
|
273
|
-
`projects/${projectId}`
|
|
274
|
-
);
|
|
275
|
-
return project?.path_with_namespace ?? String(projectId);
|
|
276
|
-
}
|
|
277
|
-
async getEvents(since) {
|
|
278
|
-
const raw = await this.getPaged("events", {
|
|
279
|
-
after: isoDay(since)
|
|
280
|
-
});
|
|
281
|
-
const ids = [...new Set(raw.map((e) => e.project_id).filter(Boolean))];
|
|
282
|
-
const paths = new Map(
|
|
283
|
-
await Promise.all(
|
|
284
|
-
ids.map(async (id) => [id, await this.projectPath(id)])
|
|
285
|
-
)
|
|
286
|
-
);
|
|
287
|
-
return raw.map((event) => {
|
|
288
|
-
const push = event.push_data ?? {};
|
|
289
|
-
return {
|
|
290
|
-
at: String(event.created_at).slice(0, 16),
|
|
291
|
-
action: event.action_name,
|
|
292
|
-
project: paths.get(event.project_id) ?? "",
|
|
293
|
-
targetType: event.target_type ?? "",
|
|
294
|
-
title: event.target_title ?? "",
|
|
295
|
-
branch: push.ref ?? "",
|
|
296
|
-
commits: push.commit_count ?? 0,
|
|
297
|
-
commitTitle: push.commit_title ?? ""
|
|
298
|
-
};
|
|
299
|
-
}).sort((a, b) => a.at.localeCompare(b.at));
|
|
300
|
-
}
|
|
301
|
-
async countUnresolved(projectId, iid) {
|
|
302
|
-
const discussions = await this.getJson(
|
|
303
|
-
`projects/${projectId}/merge_requests/${iid}/discussions`,
|
|
304
|
-
{ per_page: PAGE_SIZE }
|
|
305
|
-
);
|
|
306
|
-
if (!discussions) return 0;
|
|
307
|
-
return discussions.filter((discussion) => {
|
|
308
|
-
const notes = (discussion.notes ?? []).filter((n) => !n.system);
|
|
309
|
-
return notes.length > 0 && notes.some((n) => n.resolvable && !n.resolved);
|
|
310
|
-
}).length;
|
|
311
|
-
}
|
|
312
|
-
async shapeMr(mr) {
|
|
313
|
-
const projectId = mr.project_id;
|
|
314
|
-
const iid = mr.iid;
|
|
315
|
-
const [pipelines, unresolved] = await Promise.all([
|
|
316
|
-
this.getJson(
|
|
317
|
-
`projects/${projectId}/merge_requests/${iid}/pipelines`,
|
|
318
|
-
{ per_page: 1 }
|
|
319
|
-
),
|
|
320
|
-
this.countUnresolved(projectId, iid)
|
|
321
|
-
]);
|
|
322
|
-
const latest = pipelines?.[0];
|
|
323
|
-
return {
|
|
324
|
-
project: String(mr.references.full).split("!")[0],
|
|
325
|
-
projectId,
|
|
326
|
-
iid,
|
|
327
|
-
title: mr.title,
|
|
328
|
-
draft: mr.draft,
|
|
329
|
-
branch: mr.source_branch,
|
|
330
|
-
target: mr.target_branch,
|
|
331
|
-
updated: String(mr.updated_at).slice(0, 10),
|
|
332
|
-
url: mr.web_url,
|
|
333
|
-
mergeStatus: mr.detailed_merge_status ?? null,
|
|
334
|
-
pipeline: latest?.status ?? null,
|
|
335
|
-
pipelineId: latest?.id ?? null,
|
|
336
|
-
unresolved,
|
|
337
|
-
pipelineMissing: false,
|
|
338
|
-
bucket: "ready"
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
async getMyMrs(today) {
|
|
342
|
-
const raw = await this.getPaged("merge_requests", {
|
|
343
|
-
scope: "created_by_me",
|
|
344
|
-
state: "opened"
|
|
345
|
-
});
|
|
346
|
-
const rows = await Promise.all(raw.map((mr) => this.shapeMr(mr)));
|
|
347
|
-
markMissingPipelines(rows);
|
|
348
|
-
for (const row of rows) {
|
|
349
|
-
row.bucket = classify(row, today);
|
|
350
|
-
}
|
|
351
|
-
return rows;
|
|
352
|
-
}
|
|
353
|
-
async approvedByMe(projectId, iid, uid) {
|
|
354
|
-
const approvals = await this.getJson(`projects/${projectId}/merge_requests/${iid}/approvals`);
|
|
355
|
-
if (!approvals) return false;
|
|
356
|
-
return (approvals.approved_by ?? []).some((entry) => entry.user?.id === uid);
|
|
357
|
-
}
|
|
358
|
-
async getReviews(uid, today) {
|
|
359
|
-
const raw = await this.getPaged("merge_requests", {
|
|
360
|
-
scope: "all",
|
|
361
|
-
state: "opened",
|
|
362
|
-
reviewer_id: uid
|
|
363
|
-
});
|
|
364
|
-
const cutoff = isoDay(new Date(today.getTime() - FRESH_REVIEW_DAYS * MS_PER_DAY));
|
|
365
|
-
const rows = await Promise.all(
|
|
366
|
-
raw.map(async (mr) => ({
|
|
367
|
-
project: String(mr.references.full).split("!")[0],
|
|
368
|
-
iid: mr.iid,
|
|
369
|
-
title: mr.title,
|
|
370
|
-
author: mr.author.name,
|
|
371
|
-
updated: String(mr.updated_at).slice(0, 10),
|
|
372
|
-
draft: mr.draft,
|
|
373
|
-
url: mr.web_url,
|
|
374
|
-
fresh: String(mr.updated_at).slice(0, 10) >= cutoff,
|
|
375
|
-
approvedByMe: await this.approvedByMe(mr.project_id, mr.iid, uid)
|
|
376
|
-
}))
|
|
377
|
-
);
|
|
378
|
-
return rows.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
379
|
-
}
|
|
380
|
-
async getBlockers(mrs) {
|
|
381
|
-
const red = mrs.filter((mr) => mr.pipeline === "failed");
|
|
382
|
-
const diagnosed = await Promise.all(
|
|
383
|
-
red.map(async (mr) => {
|
|
384
|
-
const jobs = await this.getJson(
|
|
385
|
-
`projects/${mr.projectId}/pipelines/${mr.pipelineId}/jobs`,
|
|
386
|
-
{ per_page: PAGE_SIZE }
|
|
387
|
-
) ?? [];
|
|
388
|
-
const job = jobs.find((j) => j.status === "failed");
|
|
389
|
-
if (!job) return null;
|
|
390
|
-
const trace = await this.getText(
|
|
391
|
-
`projects/${mr.projectId}/jobs/${job.id}/trace`
|
|
392
|
-
);
|
|
393
|
-
return {
|
|
394
|
-
project: mr.project,
|
|
395
|
-
mr: mr.iid,
|
|
396
|
-
title: mr.title,
|
|
397
|
-
job: job.name,
|
|
398
|
-
stage: job.stage,
|
|
399
|
-
url: mr.url,
|
|
400
|
-
errors: extractErrors(trace)
|
|
401
|
-
};
|
|
402
|
-
})
|
|
403
|
-
);
|
|
404
|
-
return diagnosed.filter((row) => row !== null);
|
|
405
|
-
}
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
// src/render/render.constants.ts
|
|
409
|
-
var STRINGS = {
|
|
410
|
-
en: {
|
|
411
|
-
digest: "Structured digest \u2014 not a written note.",
|
|
412
|
-
previous: "Previous working day",
|
|
413
|
-
today: "Today",
|
|
414
|
-
ready: "Ready to merge",
|
|
415
|
-
blocked: "Blocked",
|
|
416
|
-
draft: "Drafts",
|
|
417
|
-
stale: "Stale",
|
|
418
|
-
reviews: "Reviews",
|
|
419
|
-
pending: "pending",
|
|
420
|
-
blockers: "Blockers",
|
|
421
|
-
noPipeline: "no pipeline ran",
|
|
422
|
-
unresolved: "unresolved comment(s)",
|
|
423
|
-
nothing: "No activity recorded."
|
|
424
|
-
},
|
|
425
|
-
tr: {
|
|
426
|
-
digest: "Yap\u0131land\u0131r\u0131lm\u0131\u015F d\xF6k\xFCm \u2014 yaz\u0131lm\u0131\u015F not de\u011Fil.",
|
|
427
|
-
previous: "\xD6nceki i\u015F g\xFCn\xFC",
|
|
428
|
-
today: "Bug\xFCn",
|
|
429
|
-
ready: "Merge'e haz\u0131r",
|
|
430
|
-
blocked: "Engelli",
|
|
431
|
-
draft: "Draftlar",
|
|
432
|
-
stale: "Bayat",
|
|
433
|
-
reviews: "Review",
|
|
434
|
-
pending: "bekliyor",
|
|
435
|
-
blockers: "Blocker",
|
|
436
|
-
noPipeline: "pipeline hi\xE7 \xE7al\u0131\u015Fmam\u0131\u015F",
|
|
437
|
-
unresolved: "\xE7\xF6z\xFClmemi\u015F yorum",
|
|
438
|
-
nothing: "Kay\u0131tl\u0131 aktivite yok."
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
var BUCKET_ORDER = ["ready", "blocked", "draft", "stale"];
|
|
442
|
-
|
|
443
|
-
// src/render/render.ts
|
|
444
|
-
function eventLine(event) {
|
|
445
|
-
const detail = event.commits ? `${event.commitTitle || event.branch} (${event.commits} commit)` : event.title || event.action;
|
|
446
|
-
return `- \`${event.project}\` ${event.action} \u2014 ${detail}`;
|
|
447
|
-
}
|
|
448
|
-
function toMarkdown(report, lang = "en") {
|
|
449
|
-
const t = STRINGS[lang] ?? STRINGS.en;
|
|
450
|
-
const out = [];
|
|
451
|
-
out.push(`# ${report.today.label} \u2014 ${report.user}`, "", `_${t.digest}_`, "");
|
|
452
|
-
out.push(`## ${t.previous}: ${report.previous.label ?? "\u2014"}`, "");
|
|
453
|
-
if (report.previousEvents.length > 0) {
|
|
454
|
-
out.push(...report.previousEvents.map(eventLine));
|
|
455
|
-
} else {
|
|
456
|
-
out.push(`_${t.nothing}_`);
|
|
457
|
-
}
|
|
458
|
-
out.push("");
|
|
459
|
-
if (report.todayEvents.length > 0) {
|
|
460
|
-
out.push(`## ${t.today}`, "", ...report.todayEvents.map(eventLine), "");
|
|
461
|
-
}
|
|
462
|
-
for (const bucket of BUCKET_ORDER) {
|
|
463
|
-
const rows = report.myMrs.filter((mr) => mr.bucket === bucket);
|
|
464
|
-
if (rows.length === 0) continue;
|
|
465
|
-
out.push(`## ${t[bucket]} (${rows.length})`, "");
|
|
466
|
-
for (const mr of rows) {
|
|
467
|
-
const notes = [];
|
|
468
|
-
if (mr.pipelineMissing) notes.push(t.noPipeline);
|
|
469
|
-
if (mr.unresolved) notes.push(`${mr.unresolved} ${t.unresolved}`);
|
|
470
|
-
const suffix = notes.length > 0 ? ` \u2014 **${notes.join(", ")}**` : "";
|
|
471
|
-
out.push(`- \`${mr.project}\` !${mr.iid} ${mr.title}${suffix}`);
|
|
472
|
-
}
|
|
473
|
-
out.push("");
|
|
474
|
-
}
|
|
475
|
-
const pending = report.reviews.filter((r) => !r.approvedByMe);
|
|
476
|
-
if (pending.length > 0) {
|
|
477
|
-
out.push(`## ${t.reviews} (${report.reviewPendingCount} ${t.pending})`, "");
|
|
478
|
-
for (const review of pending) {
|
|
479
|
-
out.push(
|
|
480
|
-
`- \`${review.project}\` !${review.iid} ${review.title} \u2014 ${review.author}`
|
|
481
|
-
);
|
|
482
|
-
}
|
|
483
|
-
out.push("");
|
|
484
|
-
}
|
|
485
|
-
if (report.blockers.length > 0) {
|
|
486
|
-
out.push(`## ${t.blockers}`, "");
|
|
487
|
-
for (const blocker of report.blockers) {
|
|
488
|
-
out.push(`- \`${blocker.project}\` !${blocker.mr} \u2014 job \`${blocker.job}\``);
|
|
489
|
-
out.push(...blocker.errors.map((line) => ` - \`${line}\``));
|
|
490
|
-
}
|
|
491
|
-
out.push("");
|
|
492
|
-
}
|
|
493
|
-
return `${out.join("\n").trimEnd()}
|
|
494
|
-
`;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// src/report/report.constants.ts
|
|
498
|
-
var LOOKBACK_DAYS = 21;
|
|
499
|
-
|
|
500
|
-
// src/report/report.ts
|
|
501
|
-
async function buildReport(provider, today, lang = "en", lookbackDays = LOOKBACK_DAYS) {
|
|
502
|
-
const identity = await provider.getIdentity();
|
|
503
|
-
const since = new Date(today.getTime() - lookbackDays * MS_PER_DAY);
|
|
504
|
-
const events = await provider.getEvents(since);
|
|
505
|
-
const { date: previousDate, gapDays } = previousActiveDay(
|
|
506
|
-
new Set(events.map((e) => e.at.slice(0, 10))),
|
|
507
|
-
today
|
|
508
|
-
);
|
|
509
|
-
const previousEvents = previousDate ? events.filter((e) => e.at.slice(0, 10) === previousDate) : [];
|
|
510
|
-
const todayEvents = events.filter((e) => e.at.slice(0, 10) === isoDay(today));
|
|
511
|
-
const myMrs = await provider.getMyMrs(today);
|
|
512
|
-
const [reviews, blockers] = await Promise.all([
|
|
513
|
-
provider.getReviews(identity.id, today),
|
|
514
|
-
provider.getBlockers(myMrs)
|
|
515
|
-
]);
|
|
516
|
-
return {
|
|
517
|
-
user: identity.username,
|
|
518
|
-
today: { date: isoDay(today), label: label(today, lang) },
|
|
519
|
-
previous: {
|
|
520
|
-
date: previousDate,
|
|
521
|
-
label: previousDate ? label(/* @__PURE__ */ new Date(`${previousDate}T00:00:00`), lang) : null,
|
|
522
|
-
gapDays,
|
|
523
|
-
eventCount: previousEvents.length
|
|
524
|
-
},
|
|
525
|
-
previousEvents,
|
|
526
|
-
todayEvents,
|
|
527
|
-
myMrs,
|
|
528
|
-
reviews,
|
|
529
|
-
reviewPendingCount: reviews.filter((r) => !r.approvedByMe).length,
|
|
530
|
-
blockers
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
export {
|
|
535
|
-
STALE_DAYS,
|
|
536
|
-
isoDay,
|
|
537
|
-
label,
|
|
538
|
-
previousActiveDay,
|
|
539
|
-
classify,
|
|
540
|
-
markMissingPipelines,
|
|
541
|
-
ConfigError,
|
|
542
|
-
resolveHost,
|
|
543
|
-
resolveToken,
|
|
544
|
-
glabHosts,
|
|
545
|
-
glabToken,
|
|
546
|
-
postWebhook,
|
|
547
|
-
extractErrors,
|
|
548
|
-
GitLabProvider,
|
|
549
|
-
toMarkdown,
|
|
550
|
-
buildReport
|
|
551
|
-
};
|