@mikitasazan/notify 1.1.1 → 1.1.3
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/cli.js +15 -18
- package/dist/events.d.ts +3 -2
- package/dist/render.js +6 -2
- package/dist/send.d.ts +1 -12
- package/dist/send.js +34 -19
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -163,8 +163,7 @@ else {
|
|
|
163
163
|
url: one('url'),
|
|
164
164
|
target: one('target'),
|
|
165
165
|
via: one('via'),
|
|
166
|
-
note: one('note')
|
|
167
|
-
key: one('key')
|
|
166
|
+
note: one('note')
|
|
168
167
|
};
|
|
169
168
|
break;
|
|
170
169
|
case 'job':
|
|
@@ -176,8 +175,7 @@ else {
|
|
|
176
175
|
stats: pairs('stat'),
|
|
177
176
|
items: items(),
|
|
178
177
|
note: one('note'),
|
|
179
|
-
url: one('url')
|
|
180
|
-
key: one('key')
|
|
178
|
+
url: one('url')
|
|
181
179
|
};
|
|
182
180
|
break;
|
|
183
181
|
case 'report':
|
|
@@ -188,8 +186,7 @@ else {
|
|
|
188
186
|
period: one('period'),
|
|
189
187
|
lines: pairs('line'),
|
|
190
188
|
items: items(),
|
|
191
|
-
url: one('url')
|
|
192
|
-
key: one('key')
|
|
189
|
+
url: one('url')
|
|
193
190
|
};
|
|
194
191
|
break;
|
|
195
192
|
case 'ci':
|
|
@@ -200,8 +197,7 @@ else {
|
|
|
200
197
|
branch: one('branch'),
|
|
201
198
|
commit: one('commit'),
|
|
202
199
|
actor: one('actor'),
|
|
203
|
-
url: one('url')
|
|
204
|
-
key: one('key')
|
|
200
|
+
url: one('url')
|
|
205
201
|
};
|
|
206
202
|
break;
|
|
207
203
|
case 'pr':
|
|
@@ -213,8 +209,7 @@ else {
|
|
|
213
209
|
title: one('title') ?? '(без заголовка)',
|
|
214
210
|
author: one('author'),
|
|
215
211
|
reviewer: one('reviewer'),
|
|
216
|
-
url: one('url')
|
|
217
|
-
key: one('key')
|
|
212
|
+
url: one('url')
|
|
218
213
|
};
|
|
219
214
|
break;
|
|
220
215
|
case 'issue':
|
|
@@ -226,8 +221,7 @@ else {
|
|
|
226
221
|
title: one('title') ?? '(без заголовка)',
|
|
227
222
|
author: one('author'),
|
|
228
223
|
assignee: one('assignee'),
|
|
229
|
-
url: one('url')
|
|
230
|
-
key: one('key')
|
|
224
|
+
url: one('url')
|
|
231
225
|
};
|
|
232
226
|
break;
|
|
233
227
|
case 'incident':
|
|
@@ -236,8 +230,7 @@ else {
|
|
|
236
230
|
project: project(),
|
|
237
231
|
title: one('title') ?? '(без заголовка)',
|
|
238
232
|
detail: one('detail'),
|
|
239
|
-
url: one('url')
|
|
240
|
-
key: one('key')
|
|
233
|
+
url: one('url')
|
|
241
234
|
};
|
|
242
235
|
break;
|
|
243
236
|
case 'heartbeat_miss':
|
|
@@ -246,8 +239,7 @@ else {
|
|
|
246
239
|
project: project(),
|
|
247
240
|
job: one('job') ?? '(без имени)',
|
|
248
241
|
lastSeen: one('last-seen'),
|
|
249
|
-
expected: one('expected')
|
|
250
|
-
key: one('key')
|
|
242
|
+
expected: one('expected')
|
|
251
243
|
};
|
|
252
244
|
break;
|
|
253
245
|
case 'file': {
|
|
@@ -261,8 +253,7 @@ else {
|
|
|
261
253
|
title: one('title') ?? '(без заголовка)',
|
|
262
254
|
path: path ?? '',
|
|
263
255
|
filename: one('filename'),
|
|
264
|
-
note: one('note')
|
|
265
|
-
key: one('key')
|
|
256
|
+
note: one('note')
|
|
266
257
|
};
|
|
267
258
|
break;
|
|
268
259
|
}
|
|
@@ -270,6 +261,12 @@ else {
|
|
|
270
261
|
log(`неизвестный тип события: ${command ?? '(не указан)'}`);
|
|
271
262
|
}
|
|
272
263
|
}
|
|
264
|
+
// --key применим к любому типу — одна точка вместо строки в каждом case
|
|
265
|
+
// (девять копий уже потеряли бы десятую). `??`: путь --json может нести
|
|
266
|
+
// key в самом объекте, отсутствие флага не должно его затирать.
|
|
267
|
+
if (event) {
|
|
268
|
+
event.key = one('key') ?? event.key;
|
|
269
|
+
}
|
|
273
270
|
// Ошибки разбора — до отправки: лучше внятно сказать, что не так с командой,
|
|
274
271
|
// чем прислать сообщение с «true» вместо ссылки или 🔴 на успешном деплое.
|
|
275
272
|
if (parseErrors.length > 0) {
|
package/dist/events.d.ts
CHANGED
|
@@ -16,8 +16,9 @@ export type Project = 'playhub' | 'one-q' | 'arvent' | 'game-publisher' | 'vault
|
|
|
16
16
|
* поздней карточкой того же ключа?» без сравнения человеческих формулировок,
|
|
17
17
|
* которые меняются. Необязателен: без него ключ выводится из типа и заголовка
|
|
18
18
|
* (см. `render.ts`), но выведенный наследует хрупкость формулировки — наши
|
|
19
|
-
* регулярные отправители передают его явно. В
|
|
20
|
-
*
|
|
19
|
+
* регулярные отправители передают его явно. В вывод CLI (stderr, и его же
|
|
20
|
+
* сторож на VPS читает объединённым потоком по словам `sent|failed|skipped`)
|
|
21
|
+
* ключ не попадает никогда — новое слово там ослепило бы сторожа.
|
|
21
22
|
*/
|
|
22
23
|
type Keyed = {
|
|
23
24
|
key?: string;
|
package/dist/render.js
CHANGED
|
@@ -175,7 +175,11 @@ const RENDERERS = {
|
|
|
175
175
|
const slug = (raw) => raw
|
|
176
176
|
.toLowerCase()
|
|
177
177
|
.replace(/[^\p{L}\p{N}]+/gu, '-')
|
|
178
|
-
.replace(/^-+|-+$/g, '')
|
|
178
|
+
.replace(/^-+|-+$/g, '')
|
|
179
|
+
// Ключ — идентификатор, не пересказ: без среза тег из длинного заголовка
|
|
180
|
+
// съедал бюджет caption до отрицательного, и slice с минусом возвращал
|
|
181
|
+
// почти весь текст — Telegram отвечал постоянным 400, файл терялся.
|
|
182
|
+
.slice(0, 60);
|
|
179
183
|
export const eventKey = (e) => {
|
|
180
184
|
const fallback = () => {
|
|
181
185
|
switch (e.type) {
|
|
@@ -215,6 +219,6 @@ export const render = (e) => {
|
|
|
215
219
|
// clampMessage может выйти за переданный limit на хвост закрывающих тегов и
|
|
216
220
|
// многоточие — минус 40 оставляет ему этот запас. У сообщений свой запас уже
|
|
217
221
|
// есть (4000 против 4096 у Telegram), у caption лимит 1024 настоящий.
|
|
218
|
-
const budget = e.type === 'file' ? 1024 - tag.length - 40 : 4000 - tag.length - 1;
|
|
222
|
+
const budget = Math.max(64, e.type === 'file' ? 1024 - tag.length - 40 : 4000 - tag.length - 1);
|
|
219
223
|
return `${clampMessage(renderer(e), budget)}\n${tag}`;
|
|
220
224
|
};
|
package/dist/send.d.ts
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
import type { NotifyEvent, Project } from './events.ts';
|
|
2
2
|
export type SendResult = 'sent' | 'skipped' | 'failed';
|
|
3
|
-
/**
|
|
4
|
-
* Отправляет событие во все его цели (тема проекта + при необходимости
|
|
5
|
-
* `incidents` + чат команды). Цели идут последовательно; провал одной не
|
|
6
|
-
* отменяет остальные. `'sent'`, если хотя бы одна цель получила сообщение.
|
|
7
|
-
*
|
|
8
|
-
* Неизвестный проект по-прежнему НЕ роняет вызвавший крон (код возврата не
|
|
9
|
-
* меняется) — но и не исчезает молча: в mac-config Ops уходит красная
|
|
10
|
-
* карточка. Дважды этот класс провала жил незамеченным неделями: «vault» и
|
|
11
|
-
* «mac-config» до 04.08, отчёты Alitools — до 18.08. Рекурсия невозможна:
|
|
12
|
-
* карточка-ошибка адресована mac-config, который в ROUTES есть всегда.
|
|
13
|
-
*/
|
|
14
3
|
export declare const notify: (e: NotifyEvent) => Promise<SendResult>;
|
|
15
4
|
/**
|
|
16
5
|
* Готовый HTML во вкладку «Ops» проекта — ТОЛЬКО для дневных отчётов.
|
|
@@ -29,4 +18,4 @@ export declare const notify: (e: NotifyEvent) => Promise<SendResult>;
|
|
|
29
18
|
*
|
|
30
19
|
* Всегда беззвучно: отчёт читают утром, а не по звонку.
|
|
31
20
|
*/
|
|
32
|
-
export declare const sendReport: (project: Project, html: string) => Promise<SendResult>;
|
|
21
|
+
export declare const sendReport: (project: Project, html: string, key?: string) => Promise<SendResult>;
|
package/dist/send.js
CHANGED
|
@@ -224,24 +224,28 @@ const sendFile = async (e) => {
|
|
|
224
224
|
return 'skipped';
|
|
225
225
|
}
|
|
226
226
|
const caption = render(e);
|
|
227
|
+
const results = [];
|
|
228
|
+
// Контракт тот же, что у deliver: провал одной цели не отменяет остальные.
|
|
227
229
|
for (const target of where) {
|
|
228
230
|
let waitMs = 0;
|
|
231
|
+
let got = 'failed';
|
|
229
232
|
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
230
233
|
if (waitMs > 0) {
|
|
231
234
|
await sleep(waitMs);
|
|
232
235
|
}
|
|
233
236
|
const result = await sendFileOnce(token, target, e, caption);
|
|
234
237
|
if (result.outcome === 'ok') {
|
|
235
|
-
|
|
238
|
+
got = 'sent';
|
|
239
|
+
break;
|
|
236
240
|
}
|
|
237
241
|
if (result.outcome === 'fail') {
|
|
238
|
-
|
|
242
|
+
break;
|
|
239
243
|
}
|
|
240
244
|
waitMs = result.waitMs;
|
|
241
245
|
}
|
|
246
|
+
results.push(got);
|
|
242
247
|
}
|
|
243
|
-
|
|
244
|
-
return 'failed';
|
|
248
|
+
return results.includes('sent') ? 'sent' : 'failed';
|
|
245
249
|
};
|
|
246
250
|
/**
|
|
247
251
|
* Отправляет событие во все его цели (тема проекта + при необходимости
|
|
@@ -254,17 +258,25 @@ const sendFile = async (e) => {
|
|
|
254
258
|
* «mac-config» до 04.08, отчёты Alitools — до 18.08. Рекурсия невозможна:
|
|
255
259
|
* карточка-ошибка адресована mac-config, который в ROUTES есть всегда.
|
|
256
260
|
*/
|
|
261
|
+
const reportLostProject = async (project, kind) => {
|
|
262
|
+
// Локальный лог называет и допустимые написания — это единственная
|
|
263
|
+
// диагностика, доступная на машине, где случилась опечатка.
|
|
264
|
+
log(`неизвестный проект «${String(project)}» — известны: ${Object.keys(ROUTES).join(', ')}`);
|
|
265
|
+
const lost = {
|
|
266
|
+
type: 'job',
|
|
267
|
+
project: 'mac-config',
|
|
268
|
+
job: 'notify: событие потеряно',
|
|
269
|
+
status: 'fail',
|
|
270
|
+
note: `проект «${String(project)}» не в ROUTES — событие «${kind}» никуда не доставлено`,
|
|
271
|
+
key: 'notify-unknown-project'
|
|
272
|
+
};
|
|
273
|
+
await deliver(targets(lost), render(lost)).catch(() => undefined);
|
|
274
|
+
};
|
|
257
275
|
export const notify = async (e) => {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
job: 'notify: событие потеряно',
|
|
263
|
-
status: 'fail',
|
|
264
|
-
note: `проект «${String(e.project)}» не в ROUTES — событие «${String(e.type)}» никуда не доставлено`,
|
|
265
|
-
key: 'notify-unknown-project'
|
|
266
|
-
};
|
|
267
|
-
await deliver(targets(lost), render(lost)).catch(() => undefined);
|
|
276
|
+
// Object.hasOwn, не `in`: `in` ходит по цепочке прототипов, и --project
|
|
277
|
+
// toString/constructor проходил бы гвард, терял событие И карточку о потере.
|
|
278
|
+
if (!Object.hasOwn(ROUTES, e.project)) {
|
|
279
|
+
await reportLostProject(e.project, String(e.type));
|
|
268
280
|
return 'skipped';
|
|
269
281
|
}
|
|
270
282
|
if (e.type === 'file') {
|
|
@@ -289,11 +301,14 @@ export const notify = async (e) => {
|
|
|
289
301
|
*
|
|
290
302
|
* Всегда беззвучно: отчёт читают утром, а не по звонку.
|
|
291
303
|
*/
|
|
292
|
-
export const sendReport = async (project, html) => {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
304
|
+
export const sendReport = async (project, html, key) => {
|
|
305
|
+
if (!Object.hasOwn(ROUTES, project)) {
|
|
306
|
+
// Та же красная карточка, что у notify(): дверь свободного HTML — не
|
|
307
|
+
// лазейка для молчаливой потери (этот класс уже жил неделями дважды).
|
|
308
|
+
await reportLostProject(project, 'report');
|
|
296
309
|
return 'skipped';
|
|
297
310
|
}
|
|
298
|
-
|
|
311
|
+
const forum = ROUTES[project];
|
|
312
|
+
const tag = key ? `\n<code>#${project}/${key}</code>` : '';
|
|
313
|
+
return deliver([{ chat: forum.chat, thread: forum.ops, silent: true }], clampMessage(html) + tag);
|
|
299
314
|
};
|
package/package.json
CHANGED