@mikitasazan/notify 1.7.0 → 1.8.1

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-flags.js CHANGED
@@ -14,7 +14,7 @@ export const KNOWN_FLAGS = new Set([
14
14
  'command', 'command-note', 'commit-body', 'commit-title', 'commit-url', 'detail',
15
15
  'expected', 'filename', 'item',
16
16
  'item-group', 'job', 'key', 'last-seen', 'line', 'logs', 'note',
17
- 'number', 'path', 'period', 'project', 'reviewer', 'stat', 'status',
17
+ 'aside', 'number', 'path', 'period', 'project', 'reviewer', 'stat', 'status',
18
18
  'target', 'title', 'url', 'via', 'workflow-name', 'workflow-url',
19
19
  // Флаги без значения. Живут здесь же, чтобы разбор и список не разошлись.
20
20
  'json', 'recovered', 'dry-run'
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
  }
@@ -236,6 +242,7 @@ else {
236
242
  project: project(),
237
243
  job: one('job') ?? '(no name)',
238
244
  status: jobStatus(),
245
+ aside: one('aside'),
239
246
  expected: one('expected'),
240
247
  lastSeen: one('last-seen'),
241
248
  stats: pairs('stat'),
@@ -254,7 +261,9 @@ else {
254
261
  type: 'report',
255
262
  project: project(),
256
263
  title: one('title') ?? '(no title)',
257
- period: one('period'),
264
+ // `--period` is the old spelling of `--aside`; both fill the same
265
+ // slot, and two senders still use the old one.
266
+ aside: one('aside') ?? one('period'),
258
267
  lines: pairs('line'),
259
268
  items: items(),
260
269
  url: one('url')
@@ -353,15 +362,15 @@ else {
353
362
  };
354
363
  break;
355
364
  default:
356
- // В parseErrors, а не просто в лог: иначе неизвестный тип уходил в
357
- // тишинусобытие не собиралось, ошибок разбора не было, и CLI выходил
358
- // нулём, ничего не отправив и ничего об этом не сказав.
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.
359
368
  parseErrors.push(`unknown event type: ${safe(command ?? '(none given)')}`);
360
369
  }
361
370
  }
362
- // --key применим к любому типуодна точка вместо строки в каждом case
363
- // (девять копий уже потеряли бы десятую). `??`: путь --json может нести
364
- // 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.
365
374
  if (event) {
366
375
  event.key = one('key') ?? event.key;
367
376
  // --path is applicable to any type too, for the same reason --key is.
@@ -371,8 +380,9 @@ if (event) {
371
380
  if (event?.filename && !event.path) {
372
381
  parseErrors.push('--filename: given without --path, so there is no file to name');
373
382
  }
374
- // Ошибки разборадо отправки: лучше внятно сказать, что не так с командой,
375
- // чем прислать сообщение с «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.
376
386
  if (parseErrors.length > 0) {
377
387
  for (const err of parseErrors) {
378
388
  log(err);
@@ -400,15 +410,17 @@ if (event && flags.has('dry-run')) {
400
410
  process.exit(0);
401
411
  }
402
412
  if (event) {
403
- // Ловим ВСЁ: уведомление не имеет права уронить деплой или крон, который
404
- // его вызвал. В bash с `set -e` (или в `trap ... ERR`) ненулевой код здесь
405
- // завалил бы саму задачу ровно то, чего пакет обязан не делать.
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.
406
417
  try {
407
418
  log(await notify(event));
408
419
  }
409
420
  catch (err) {
410
- // Слово `failed` контракт, тот же, что у ошибки разбора выше. Без него
411
- // исключение при отправке читалось сторожами как «ничего не случилось».
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."
412
424
  log(`failed: ${safe(err instanceof Error ? err.message : err)}`);
413
425
  }
414
426
  }