@mikitasazan/notify 1.8.0 → 1.9.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/README.md CHANGED
@@ -95,17 +95,18 @@ notify report --project playhub --json < payload.json # весь объект
95
95
 
96
96
  Полные сигнатуры — `src/events.ts`.
97
97
 
98
- **Ключ задачи.** Первая строка каждой карточки — два тега: `#тип #ключ`.
98
+ **Ключ задачи.** Первая строка каждой карточки — три тега: `#тип #ключ
99
+ #итог`.
99
100
  По ним дневной разборщик сверяет «это 🔴 уже закрыто более поздней карточкой
100
101
  той же задачи?» без сравнения человеческих формулировок. Явный `--key`
101
102
  побеждает; без него ключ выводится из заголовка и меняется вместе с ним —
102
103
  регулярный отправитель передаёт `--key` явно. В stdout CLI ключ не попадает.
103
104
 
104
- **Единственная дверь для свободного HTML** `sendReport()` (`report --json` у
105
- аналитик): плотную строку дневного отчёта не разложить в `label=value` не
106
- испортив. Стандартизирован транспорт, формат тела нет, и только здесь.
107
- Строку тегов `#report #ключ` пакет ставит и здесь: тег — фильтр владельца, к
108
- формату тела он отношения не имеет. Без явного ключа берётся `daily`.
105
+ **Свободного HTML в пакете нет.** Дверь `sendReport()` удалена 25.08.2026:
106
+ она отдавала произвольную разметку и ставила карточке два тега вместо трёх —
107
+ единственный вид, который нельзя было отфильтровать по итогу. Последний её
108
+ отправитель перешёл на типизированный `report` ещё 25.08; тип события задаёт
109
+ каждый знак карточки, других путей нет.
109
110
 
110
111
  **Неизвестный проект** не роняет вызвавший крон (код возврата 0), но больше и
111
112
  не исчезает молча: в mac-config Ops уходит красная карточка «notify: событие
package/dist/cli.js CHANGED
@@ -1,24 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * `notify <type> [--flag value]...` — тонкий диспетчер. Ноль зависимостей:
4
- * разбор аргументов написан руками (не yargs/commander), потому что здесь
5
- * нужно ровно два вида флагов (одиночный и повторяемый `key=value`).
3
+ * `notify <type> [--flag value]...` — a thin dispatcher. Zero dependencies:
4
+ * argument parsing is written by hand (not yargs/commander), because it
5
+ * needs exactly two kinds of flags (a single one and a repeatable
6
+ * `key=value`).
6
7
  *
7
- * Код возврата ВСЕГДА 0 — уведомление не имеет права уронить вызвавший его
8
- * деплой или задачу. Все ошибки только в stderr. Исключения намеренно нет
9
- * (см. docs/rollout.md «чего не делаем»): сценария, где деплой должен упасть
10
- * из-за неотправленного сообщения, не существует.
8
+ * The exit code is ALWAYS 0 — a notification has no right to bring down the
9
+ * deploy or task that called it. All errors go to stderr only. There is
10
+ * deliberately no exception (see docs/rollout.md "what we don't do"): there
11
+ * is no scenario where a deploy should fail because a message did not send.
11
12
  *
12
13
  * notify deploy --project playhub --status ok --commit "msg" [--commit-url "..."] --url "..."
13
- * notify job --project playhub --job "Импорт игр" --status ok --stat "добавлено=5"
14
- * notify report --project playhub --title "Сводка за день" --line "Игр=1284"
14
+ * notify job --project playhub --job "Game import" --status ok --stat "added=5"
15
+ * notify report --project playhub --title "Daily summary" --line "Games=1284"
15
16
  * notify ci --project arvent --status fail --branch master --actor saz_sam
16
17
  * notify pr --project arvent --action opened --number 142 --title "..."
17
- * notify incident --project arvent --title "Redis недоступен" --detail "$ERR"
18
- * notify file --project arvent --title "Полные диалоги" --path ./out.txt [--filename имя.txt]
19
- * notify <type> [--key стабильный-ключ] # ключ задачи в последней строке карточки
20
- * notify <type> --json < payload.json # весь объект события со stdin
21
- * notify setup <chat_id форума> <ключ-проекта> # создать вкладки, см. setup.ts
18
+ * notify incident --project arvent --title "Redis is unreachable" --detail "$ERR"
19
+ * notify file --project arvent --title "Full dialogues" --path ./out.txt [--filename name.txt]
20
+ * notify <type> [--key stable-key] # the task's key on the card's last line
21
+ * notify <type> --json < payload.json # the whole event object on stdin
22
+ * notify setup <forum chat_id> <project key> # create the tabs, see setup.ts
22
23
  */
23
24
  import { readFileSync } from 'node:fs';
24
25
  import { KNOWN_FLAGS } from "./cli-flags.js";
@@ -52,7 +53,7 @@ if (command === 'setup') {
52
53
  }
53
54
  const flags = new Map();
54
55
  const parseErrors = [];
55
- /** Флаги без значения. Всё остальное обязано его иметь. */
56
+ /** Flags with no value. Everything else must have one. */
56
57
  const BOOLEAN_FLAGS = new Set(['json', 'recovered', 'dry-run']);
57
58
  for (let i = 1; i < args.length; i++) {
58
59
  const arg = args[i];
@@ -60,8 +61,8 @@ for (let i = 1; i < args.length; i++) {
60
61
  parseErrors.push(`stray argument with no flag: "${safe(arg)}"`);
61
62
  continue;
62
63
  }
63
- // Форма `--key=value` обязательна для значений, начинающихся с `--`
64
- // (текст ошибки, кусок диффа): иначе они были бы съедены как флаги.
64
+ // The `--key=value` form is required for values that start with `--`
65
+ // (an error message, a diff chunk): otherwise they would get eaten as flags.
65
66
  const eq = arg.indexOf('=');
66
67
  if (eq !== -1) {
67
68
  const key = arg.slice(2, eq);
@@ -74,10 +75,10 @@ for (let i = 1; i < args.length; i++) {
74
75
  continue;
75
76
  }
76
77
  const next = args[i + 1];
77
- // Раньше флаг без значения молча становился строкой 'true'. Отсюда
78
- // `--url` в конце команды давал `href="true"`, Telegram отвечал 400 и
79
- // ТЕРЯЛОСЬ ВСЁ сообщение, а `--status` без значения рисовал 🔴 на
80
- // успешном деплое. Теперь это явная ошибка разбора.
78
+ // A flag with no value used to silently become the string 'true'. That is
79
+ // how `--url` at the end of a command produced `href="true"`, Telegram
80
+ // answered 400 and THE WHOLE MESSAGE WAS LOST, and `--status` with no value
81
+ // painted 🔴 on a successful deploy. Now it is an explicit parse error.
81
82
  if (next === undefined || next.startsWith('--')) {
82
83
  parseErrors.push(`flag --${safe(key)} with no value`);
83
84
  continue;
@@ -91,7 +92,7 @@ for (const key of flags.keys()) {
91
92
  parseErrors.push(`unknown flag --${safe(key)}`);
92
93
  }
93
94
  }
94
- // Число с явной ошибкой разбора, иначе рендер рисовал «PR #NaN».
95
+ // A number with an explicit parse error, otherwise the render drew "PR #NaN".
95
96
  const num = (key) => {
96
97
  const raw = one(key);
97
98
  const n = Number(raw);
@@ -102,12 +103,13 @@ const num = (key) => {
102
103
  return n;
103
104
  };
104
105
  /**
105
- * `--item "текст"` или `--item "текст|https://ссылка"`.
106
+ * `--item "text"` or `--item "text|https://link"`.
106
107
  *
107
- * Имя группы у позиции нельзя передать так же, как у `--stat`: черта здесь
108
- * уже занята ссылкой. Поэтому `--item-group "Red checks"` — одно имя на все
109
- * позиции этого вызова. Списки у отправителей однородные (красные проверки,
110
- * выключенные процессы), а разнородный список это `--json`.
108
+ * An item's group name cannot be passed the same way `--stat` does it: the
109
+ * bar here is already taken by the link. So `--item-group "Red checks"` is
110
+ * one name for all the items in this call. Senders' lists are homogeneous
111
+ * (red checks, disabled processes), and a mixed list is what `--json` is
112
+ * for.
111
113
  */
112
114
  const items = () => {
113
115
  const name = flags.get('item-group')?.[0];
@@ -118,13 +120,13 @@ const items = () => {
118
120
  });
119
121
  };
120
122
  /**
121
- * `--stat "label=value"`, и с 25.08.2026 — `--stat "Group | label=value"`:
122
- * имя группы, вертикальная черта, ярлык. Черта выбрана потому, что её нет ни
123
- * в одном живом ярлыке, а двоеточие есть («Eval: bot answer quality») и
124
- * равенство занято значением. Пробелы вокруг черты необязательны.
123
+ * `--stat "label=value"`, and since 25.08.2026 — `--stat "Group | label=value"`:
124
+ * group name, vertical bar, label. The bar was chosen because it appears in
125
+ * no live label, while a colon does ("Eval: bot answer quality") and equals
126
+ * is taken by the value. Spaces around the bar are optional.
125
127
  *
126
- * Без черты всё как было так шлют больше двадцати отправителей, и ни один
127
- * из них менять не нужно.
128
+ * Without the bar everything works as before over twenty senders send it
129
+ * that way, and not one of them needs to change.
128
130
  */
129
131
  const pairs = (key) => (flags.get(key) ?? []).map((s) => {
130
132
  const idx = s.indexOf('=');
@@ -154,9 +156,10 @@ const ISSUE_ALIASES = {
154
156
  assigned: 'assigned',
155
157
  closed: 'closed'
156
158
  };
157
- // Ошибка кладётся в parseErrors — тот же путь, что у остального разбора: ниже
158
- // он печатает все ошибки разом и выходит ДО отправки. Значение-заглушка нужно
159
- // только чтобы удовлетворить тип, до сети оно не доживёт.
159
+ // The error goes into parseErrors — the same path the rest of parsing takes:
160
+ // below, it prints all the errors together and exits BEFORE sending. The
161
+ // placeholder value only needs to satisfy the type; it never lives to reach
162
+ // the network.
160
163
  const prAction = (raw) => {
161
164
  const hit = PR_ALIASES[(raw ?? '').toLowerCase()];
162
165
  if (!hit) {
@@ -174,19 +177,21 @@ const issueAction = (raw) => {
174
177
  return hit;
175
178
  };
176
179
  /**
177
- * Всё, что не признано успехом, считается провалом.
180
+ * Anything not recognized as a success counts as a failure.
178
181
  *
179
- * Тут важна не строгость, а согласованность: раньше `--status success`
180
- * (естественная опечатка при ручном вызове) рисовал 🔴 «упал», но `severity()`
181
- * видел «не fail» и слал сообщение БЕЗ звука. Красная плашка без звука —
182
- * худший исход: авария выглядит аварией, но не будит.
182
+ * What matters here is not strictness but consistency: `--status success`
183
+ * used to (a natural typo on a manual call) paint 🔴 "failed," but
184
+ * `severity()` saw "not fail" and sent the message WITH NO SOUND. A red
185
+ * card with no sound is the worst outcome: it looks like an incident but
186
+ * does not wake anyone up.
183
187
  */
184
188
  const status = () => {
185
189
  const raw = (one('status') ?? '').toLowerCase();
186
190
  return raw === 'ok' || raw === 'success' || raw === 'passed' || raw === '0' ? 'ok' : 'fail';
187
191
  };
188
- // `job` единственный тип с третьим состоянием (`disabled`): задача не
189
- // провалилась сама, её выключил кто-то извне (GitHub Actions без минут).
192
+ // `job` is the only type with a third state (`disabled`): the task did not
193
+ // fail on its own, someone switched it off from outside (GitHub Actions out
194
+ // of minutes).
190
195
  const jobStatus = () => {
191
196
  const raw = (one('status') ?? '').toLowerCase();
192
197
  if (raw === 'disabled') {
@@ -201,13 +206,14 @@ let event;
201
206
  if (flags.has('json')) {
202
207
  try {
203
208
  const payload = JSON.parse(readFileSync(0, 'utf-8'));
204
- // type за командой, не за payload: иначе --pr <объект с type:deploy>
205
- // отправил бы событие другого типа.
209
+ // type comes from the command, not from the payload: otherwise --pr
210
+ // <object with type:deploy> would send an event of a different type.
206
211
  event = { ...payload, type: command };
207
212
  }
208
213
  catch (err) {
209
- // Тоже в parseErrors: обе аналитики зовут CLI через `|| true`, и молчащий
210
- // разбор JSON означал бы зелёный крон без дневного отчёта.
214
+ // Also into parseErrors: both analytics jobs call the CLI through
215
+ // `|| true`, and a silent JSON parse failure would mean a green cron run
216
+ // with no daily report.
211
217
  parseErrors.push(`--json from stdin did not parse: ${safe(err instanceof Error ? err.message : err)}`);
212
218
  }
213
219
  }
@@ -356,15 +362,15 @@ else {
356
362
  };
357
363
  break;
358
364
  default:
359
- // В parseErrors, а не просто в лог: иначе неизвестный тип уходил в
360
- // тишинусобытие не собиралось, ошибок разбора не было, и CLI выходил
361
- // нулём, ничего не отправив и ничего об этом не сказав.
365
+ // Into parseErrors, not just into the log: otherwise an unknown type
366
+ // went quiet no event was built, there were no parse errors, and
367
+ // the CLI exited zero, sending nothing and saying nothing about it.
362
368
  parseErrors.push(`unknown event type: ${safe(command ?? '(none given)')}`);
363
369
  }
364
370
  }
365
- // --key применим к любому типуодна точка вместо строки в каждом case
366
- // (девять копий уже потеряли бы десятую). `??`: путь --json может нести
367
- // key в самом объекте, отсутствие флага не должно его затирать.
371
+ // --key applies to any typeone spot instead of a line in every case
372
+ // (nine copies would already have lost the tenth). `??`: the --json path
373
+ // may carry key in the object itself, a missing flag must not overwrite it.
368
374
  if (event) {
369
375
  event.key = one('key') ?? event.key;
370
376
  // --path is applicable to any type too, for the same reason --key is.
@@ -374,8 +380,9 @@ if (event) {
374
380
  if (event?.filename && !event.path) {
375
381
  parseErrors.push('--filename: given without --path, so there is no file to name');
376
382
  }
377
- // Ошибки разборадо отправки: лучше внятно сказать, что не так с командой,
378
- // чем прислать сообщение с «true» вместо ссылки или 🔴 на успешном деплое.
383
+ // Parse errorsbefore sending: better to say clearly what is wrong with
384
+ // the command than to send a message with "true" instead of a link, or a
385
+ // 🔴 on a successful deploy.
379
386
  if (parseErrors.length > 0) {
380
387
  for (const err of parseErrors) {
381
388
  log(err);
@@ -403,15 +410,17 @@ if (event && flags.has('dry-run')) {
403
410
  process.exit(0);
404
411
  }
405
412
  if (event) {
406
- // Ловим ВСЁ: уведомление не имеет права уронить деплой или крон, который
407
- // его вызвал. В bash с `set -e` (или в `trap ... ERR`) ненулевой код здесь
408
- // завалил бы саму задачу ровно то, чего пакет обязан не делать.
413
+ // Catches EVERYTHING: a notification has no right to bring down the
414
+ // deploy or cron job that called it. In bash with `set -e` (or in
415
+ // `trap ... ERR`) a non-zero code here would fail the task itself — the
416
+ // exact thing this package must never do.
409
417
  try {
410
418
  log(await notify(event));
411
419
  }
412
420
  catch (err) {
413
- // Слово `failed` контракт, тот же, что у ошибки разбора выше. Без него
414
- // исключение при отправке читалось сторожами как «ничего не случилось».
421
+ // The word `failed` is a contract, the same one the parse error above
422
+ // uses. Without it, an exception while sending read to watchdogs as
423
+ // "nothing happened."
415
424
  log(`failed: ${safe(err instanceof Error ? err.message : err)}`);
416
425
  }
417
426
  }