@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 +1 -1
- package/dist/cli.js +73 -61
- package/dist/events.d.ts +142 -118
- package/dist/events.js +23 -21
- package/dist/render.d.ts +48 -45
- package/dist/render.js +233 -198
- package/dist/routes.d.ts +31 -27
- package/dist/routes.js +25 -20
- package/package.json +1 -1
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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* (
|
|
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 "
|
|
14
|
-
* notify report --project playhub --title "
|
|
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
|
|
18
|
-
* notify file --project arvent --title "
|
|
19
|
-
* notify <type> [--key
|
|
20
|
-
* notify <type> --json < payload.json #
|
|
21
|
-
* notify setup <chat_id
|
|
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
|
-
//
|
|
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
|
-
//
|
|
78
|
-
// `--url`
|
|
79
|
-
//
|
|
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
|
-
//
|
|
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 "
|
|
106
|
+
* `--item "text"` or `--item "text|https://link"`.
|
|
106
107
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
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"`,
|
|
122
|
-
*
|
|
123
|
-
*
|
|
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
|
-
//
|
|
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
|
-
*
|
|
180
|
-
* (
|
|
181
|
-
*
|
|
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`
|
|
189
|
-
//
|
|
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
|
|
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
|
-
//
|
|
210
|
-
//
|
|
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
|
|
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
|
-
//
|
|
357
|
-
//
|
|
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
|
|
363
|
-
// (
|
|
364
|
-
// key
|
|
371
|
+
// --key applies to any type — one 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
|
-
//
|
|
383
|
+
// Parse errors — before 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
|
-
//
|
|
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
|
-
//
|
|
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
|
}
|