@mikitasazan/notify 1.4.4 → 1.5.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/dist/cli-flags.js +2 -1
- package/dist/cli.js +17 -6
- package/dist/render.js +76 -67
- package/package.json +1 -1
package/dist/cli-flags.js
CHANGED
|
@@ -12,7 +12,8 @@ export const KNOWN_FLAGS = new Set([
|
|
|
12
12
|
'action', 'actor', 'assignee', 'author', 'body', 'branch', 'commit',
|
|
13
13
|
'id', 'opened', 'reason', 'workdir',
|
|
14
14
|
'command', 'command-note', 'commit-body', 'commit-title', 'commit-url', 'detail',
|
|
15
|
-
'expected', 'filename', 'item',
|
|
15
|
+
'expected', 'filename', 'item',
|
|
16
|
+
'item-group', 'job', 'key', 'last-seen', 'line', 'logs', 'note',
|
|
16
17
|
'number', 'path', 'period', 'project', 'reviewer', 'stat', 'status',
|
|
17
18
|
'target', 'title', 'url', 'via', 'workflow-name', 'workflow-url',
|
|
18
19
|
// Флаги без значения. Живут здесь же, чтобы разбор и список не разошлись.
|
package/dist/cli.js
CHANGED
|
@@ -101,11 +101,22 @@ const num = (key) => {
|
|
|
101
101
|
}
|
|
102
102
|
return n;
|
|
103
103
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
/**
|
|
105
|
+
* `--item "текст"` или `--item "текст|https://ссылка"`.
|
|
106
|
+
*
|
|
107
|
+
* Имя группы у позиции нельзя передать так же, как у `--stat`: черта здесь
|
|
108
|
+
* уже занята ссылкой. Поэтому `--item-group "Red checks"` — одно имя на все
|
|
109
|
+
* позиции этого вызова. Списки у отправителей однородные (красные проверки,
|
|
110
|
+
* выключенные процессы), а разнородный список — это `--json`.
|
|
111
|
+
*/
|
|
112
|
+
const items = () => {
|
|
113
|
+
const name = flags.get('item-group')?.[0];
|
|
114
|
+
return (flags.get('item') ?? []).map((raw) => {
|
|
115
|
+
const idx = raw.lastIndexOf('|');
|
|
116
|
+
const base = idx === -1 ? { text: raw } : { text: raw.slice(0, idx), url: raw.slice(idx + 1) };
|
|
117
|
+
return name ? { ...base, group: name } : base;
|
|
118
|
+
});
|
|
119
|
+
};
|
|
109
120
|
/**
|
|
110
121
|
* `--stat "label=value"`, и с 25.08.2026 — `--stat "Group | label=value"`:
|
|
111
122
|
* имя группы, вертикальная черта, ярлык. Черта выбрана потому, что её нет ни
|
|
@@ -337,7 +348,7 @@ else {
|
|
|
337
348
|
type: 'report',
|
|
338
349
|
project: project(),
|
|
339
350
|
title: one('title') ?? '(no title)',
|
|
340
|
-
period
|
|
351
|
+
// A file card has no period; the caption is its title.
|
|
341
352
|
lines: []
|
|
342
353
|
};
|
|
343
354
|
break;
|
package/dist/render.js
CHANGED
|
@@ -164,7 +164,11 @@ const fieldCode = (label, value) => value ? `<b>${esc(cap(label))}:</b> <code>${
|
|
|
164
164
|
*/
|
|
165
165
|
const fieldRun = (value, why) => {
|
|
166
166
|
const explain = field('To do', why);
|
|
167
|
-
|
|
167
|
+
// Без значка. `▶` был единственным символом такого рода на все двадцать
|
|
168
|
+
// видов карточек, и владелец справедливо спросил, что он значит: ничего,
|
|
169
|
+
// чего не сказала бы строка `To do:` над ним и моноширинный шрифт под ним —
|
|
170
|
+
// Telegram делает такую строку копируемой по нажатию сам.
|
|
171
|
+
return value && explain !== null ? [explain, `<code>${esc(value)}</code>`] : [];
|
|
168
172
|
};
|
|
169
173
|
/** Заголовок группы: курсив + подчёркивание, без жирности, без двоеточия. */
|
|
170
174
|
const group = (name) => `<i><u>${esc(cap(name))}</u></i>`;
|
|
@@ -341,14 +345,21 @@ const twoBlocks = (run, change) => {
|
|
|
341
345
|
// report and asked what "open" was — the answer is the report itself, which was
|
|
342
346
|
// sitting three lines above as dead text. So line 2 takes an optional URL and
|
|
343
347
|
// the action text becomes the link: `Report: <a>Analytics for 12.08</a>`.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
+
// `aside` is the one qualifier an identifier needs to be readable on its own —
|
|
349
|
+
// which day a report covers, which day its arrows are measured against. It
|
|
350
|
+
// rides in brackets ON the type line instead of taking a row of its own,
|
|
351
|
+
// because a row of its own reads as another fact about the subject rather than
|
|
352
|
+
// as part of the name.
|
|
353
|
+
const typeLine = (icon, type, action, url, aside) => {
|
|
354
|
+
// `field` returns null on an empty value, and interpolating null into a
|
|
355
|
+
// template prints the word "null". That is how line 2 of a card became
|
|
356
|
+
// `ℹ️ null` — reachable through `--json` and through a direct call from JS,
|
|
357
|
+
// where there are no types.
|
|
348
358
|
// `action || 'open'` in the linked case: an empty title must not swallow the
|
|
349
359
|
// link, which would be the one thing the card cannot afford to lose.
|
|
350
360
|
const line = url ? fieldLink(type, url, action || 'open') : field(type, action);
|
|
351
|
-
|
|
361
|
+
const tail = aside ? ` (${esc(aside)})` : '';
|
|
362
|
+
return line === null ? `${icon} <b>${esc(cap(type))}</b>${tail}` : `${icon} ${line}${tail}`;
|
|
352
363
|
};
|
|
353
364
|
// `workflowUrl ?? url`: половина отправителей шлёт ссылку на прогон под именем
|
|
354
365
|
// `--url` — это имя было в пакете раньше и осталось в вызовах. Рендер читал
|
|
@@ -382,8 +393,10 @@ const renderDeploy = (e) => {
|
|
|
382
393
|
const icon = iconFor(e);
|
|
383
394
|
const runUrl = e.workflowUrl ?? e.url;
|
|
384
395
|
return join([
|
|
385
|
-
|
|
386
|
-
|
|
396
|
+
// Имя того, чем выкатили, — на строке типа. Исход говорят значок и третий
|
|
397
|
+
// тег; повторять его словом нечего, и это тот же закон, что у задачи и у
|
|
398
|
+
// отчёта. Строки `Via` больше нет: она несла это имя этажом ниже.
|
|
399
|
+
typeLine(icon, 'Deploy', mechanism(e.workflowName, e.via, runUrl) ?? e.status, runUrl),
|
|
387
400
|
...twoBlocks([field('Target', e.target), field('Reason', e.note)], [fieldLink('Commit', e.commitUrl, e.commit), titleField(e.commitTitle), bodyQuote(e.commitBody)])
|
|
388
401
|
]);
|
|
389
402
|
};
|
|
@@ -391,18 +404,23 @@ const renderJob = (e) => {
|
|
|
391
404
|
const icon = iconFor(e);
|
|
392
405
|
const hasItems = (e.items ?? []).length > 0;
|
|
393
406
|
const disabledList = hasItems && e.status === 'disabled';
|
|
407
|
+
// The NAME goes on the type line, the way `Report:` and `Issue:` carry
|
|
408
|
+
// theirs. It used to sit a line below under a second label, `Task:`, and
|
|
409
|
+
// the owner asked what a task is doing on a card headed Job — nothing: they
|
|
410
|
+
// were two words for one thing, and the outcome that took the first line is
|
|
411
|
+
// already the icon, and now the third tag too.
|
|
412
|
+
//
|
|
413
|
+
// `disabled` and `silent` keep a word of their own. A red mark reads as
|
|
414
|
+
// "it broke", and neither of those is that: one was switched off on purpose
|
|
415
|
+
// and the other has not been heard from at all.
|
|
416
|
+
const state = e.status === 'disabled'
|
|
417
|
+
? 'switched off, not broken'
|
|
418
|
+
: e.status === 'silent'
|
|
419
|
+
? 'no word from it at all'
|
|
420
|
+
: undefined;
|
|
394
421
|
return join([
|
|
395
|
-
typeLine(icon, 'Job', e.
|
|
396
|
-
'',
|
|
397
|
-
// The name is NOT the type line: line 2 is `Job: fail` by the format's own
|
|
398
|
-
// rule, so the name is its own field. Called `Task:` and not `Job:` because
|
|
399
|
-
// repeating the label of the line right above it reads as a mistake.
|
|
400
|
-
// Until now the name was dropped entirely — every caller passed it and the
|
|
401
|
-
// owner only ever saw it as the small grey instance tag.
|
|
402
|
-
// The run link rides on the task's own name. It used to sit at the bottom
|
|
403
|
-
// as `Workflow: open` — every job caller passes a URL and none passes a
|
|
404
|
-
// workflow name, so that row was the bare verb the owner objected to.
|
|
405
|
-
fieldLink('Task', e.workflowUrl ?? e.url, e.job),
|
|
422
|
+
typeLine(icon, 'Job', e.job, e.workflowUrl ?? e.url),
|
|
423
|
+
field('State', state),
|
|
406
424
|
field('Reason', e.note),
|
|
407
425
|
field('Expected', e.expected),
|
|
408
426
|
// `Last run` when the task is alive, `Last seen` when it is not: the same
|
|
@@ -427,18 +445,15 @@ const renderJob = (e) => {
|
|
|
427
445
|
const renderReport = (e) => {
|
|
428
446
|
if (e.groups && e.groups.length > 0) {
|
|
429
447
|
const body = e.groups.flatMap((g, i) => (i === 0 ? renderGroup(g) : ['', ...renderGroup(g)]));
|
|
430
|
-
// `lines`
|
|
431
|
-
//
|
|
432
|
-
//
|
|
448
|
+
// `lines` AND `groups` together, not one or the other: the branch with
|
|
449
|
+
// groups used to print ONLY the groups, and the report's numbers vanished
|
|
450
|
+
// without a word.
|
|
433
451
|
const numbers = labelled(e.lines);
|
|
434
452
|
return join([
|
|
435
|
-
typeLine(iconFor(e), 'Report', e.title, e.url),
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
// Владелец: «период пошёл не туда, он же должен быть рядом с датой».
|
|
440
|
-
field('Period', e.period),
|
|
441
|
-
'',
|
|
453
|
+
typeLine(iconFor(e), 'Report', e.title, e.url, e.period),
|
|
454
|
+
// Rows with no group of their own sit flush against the header instead of
|
|
455
|
+
// forming a separate slab under a blank line. `labelled` puts the blank
|
|
456
|
+
// line before the first group itself, so there is none here.
|
|
442
457
|
...numbers,
|
|
443
458
|
body.length > 0 ? '' : null,
|
|
444
459
|
...body
|
|
@@ -448,10 +463,8 @@ const renderReport = (e) => {
|
|
|
448
463
|
return join([
|
|
449
464
|
// Both analytics jobs send a link to the day's snapshot in docs/. It used to
|
|
450
465
|
// hang off a trailing `Details: open` row; now it is the report's own name.
|
|
451
|
-
typeLine(iconFor(e), 'Report', e.title, e.url),
|
|
452
|
-
//
|
|
453
|
-
field('Period', e.period),
|
|
454
|
-
'',
|
|
466
|
+
typeLine(iconFor(e), 'Report', e.title, e.url, e.period),
|
|
467
|
+
// Flush against the header — see the branch above.
|
|
455
468
|
...labelled(e.lines),
|
|
456
469
|
items.length > 0 ? '' : null,
|
|
457
470
|
...items
|
|
@@ -465,61 +478,54 @@ const renderCi = (e) => {
|
|
|
465
478
|
const icon = iconFor(e);
|
|
466
479
|
const runUrl = e.workflowUrl ?? e.url;
|
|
467
480
|
return join([
|
|
468
|
-
typeLine(icon, 'CI', e.status),
|
|
469
|
-
fieldLink('Check', runUrl, mechanism(e.workflowName, undefined, runUrl)),
|
|
481
|
+
typeLine(icon, 'CI', mechanism(e.workflowName, undefined, runUrl) ?? e.status, runUrl),
|
|
470
482
|
...twoBlocks([field('Actor', e.actor), field('Reason', e.note)], [fieldLink('Commit', e.commitUrl, e.commit), titleField(e.commitTitle), bodyQuote(e.commitBody)])
|
|
471
483
|
]);
|
|
472
484
|
};
|
|
485
|
+
// A pull request and an issue are identified the way GitHub itself identifies
|
|
486
|
+
// them: `#118 <title>`, one string, and it is the link. It used to take three
|
|
487
|
+
// rows — the action on line 2, `Number:` under it, `Title:` under that — so
|
|
488
|
+
// the thing the card is about could not be read without reading three lines.
|
|
489
|
+
// The action is not repeated in words: the icon carries it, and no two actions
|
|
490
|
+
// of one type share an icon.
|
|
491
|
+
const named = (number, title) => title ? `#${number} ${title}` : `#${number}`;
|
|
473
492
|
const renderPr = (e) => join([
|
|
474
|
-
typeLine(iconFor(e), 'PR', e.
|
|
475
|
-
'',
|
|
476
|
-
// Идентификатор первым, заголовок под ним: так вещь читается «#118, вот
|
|
477
|
-
// такая», а не «вот такая, кстати #118» — и так её пишет сам GitHub.
|
|
478
|
-
fieldLink('Number', e.url, `#${e.number}`),
|
|
479
|
-
titleField(e.title),
|
|
493
|
+
typeLine(iconFor(e), 'PR', named(e.number, e.title), e.url),
|
|
480
494
|
bodyQuote(e.body),
|
|
481
|
-
// Без пустой строки перед автором: у задачи её нет, и одно и то же поле
|
|
482
|
-
// не должно стоять по-разному в двух соседних карточках. Пустая строка в
|
|
483
|
-
// этом формате означает «дальше указатель, куда пойти» — автор не он.
|
|
484
495
|
field('Author', e.author),
|
|
485
496
|
field('Reviewer', e.reviewer)
|
|
486
497
|
]);
|
|
487
498
|
const renderIssue = (e) => join([
|
|
488
|
-
typeLine(iconFor(e), 'Issue', e.
|
|
489
|
-
'',
|
|
490
|
-
fieldLink('Number', e.url, `#${e.number}`),
|
|
491
|
-
titleField(e.title),
|
|
499
|
+
typeLine(iconFor(e), 'Issue', named(e.number, e.title), e.url),
|
|
492
500
|
bodyQuote(e.body),
|
|
493
501
|
field('Author', e.author),
|
|
494
502
|
field('Assignee', e.assignee)
|
|
495
503
|
]);
|
|
504
|
+
// The incident's own title IS line 2, exactly as an issue's is. It used to say
|
|
505
|
+
// the word `open` there — which the 🚨 already says, and no other card repeats
|
|
506
|
+
// its icon in words — with the real title one row below under a `Title:` label.
|
|
507
|
+
//
|
|
508
|
+
// `detail` is a diagnosis of several lines (vault greps three of them plus a
|
|
509
|
+
// log path). It used to go through `field`, which keeps only the first line, so
|
|
510
|
+
// every alarm this package ever sent arrived gutted. It is quoted now, the same
|
|
511
|
+
// shape a commit body takes.
|
|
496
512
|
const renderIncident = (e) => join([
|
|
497
|
-
typeLine(iconFor(e), 'Incident',
|
|
498
|
-
'',
|
|
499
|
-
// `detail` is a diagnosis of several lines (vault greps three of them plus a
|
|
500
|
-
// log path). It used to go through `field`, which keeps only the first line,
|
|
501
|
-
// so every alarm this package ever sent arrived gutted. Same shape as a
|
|
502
|
-
// commit now: short label, full text quoted under it.
|
|
503
|
-
// Ярлык `Title`, а не `Reason`: у аварии заголовок — такой же заголовок,
|
|
504
|
-
// как у коммита и задачи, и называться в одной карточке он должен так же.
|
|
505
|
-
// Same rule as the report: the link rides on the incident's own title
|
|
506
|
-
// rather than on a trailing row whose only text is the word `open`.
|
|
507
|
-
fieldLink('Title', e.url, e.title),
|
|
513
|
+
typeLine(iconFor(e), 'Incident', e.title, e.url),
|
|
508
514
|
e.detail && e.detail !== e.title ? note(e.detail) : null,
|
|
509
515
|
e.logs ? '' : null,
|
|
510
516
|
fieldCode('Logs', e.logs)
|
|
511
517
|
]);
|
|
512
|
-
// Раньше всё это склеивалось в одну строку `Reason:` через тире: «имя — no
|
|
513
|
-
// reports — expected X, last seen Y». Каждая другая карточка кладёт факт на
|
|
514
|
-
// свою строку с ярлыком, и владелец справедливо спросил, зачем тут отдельный
|
|
515
|
-
// формат. Отдельного формата больше нет.
|
|
516
518
|
// A session in trouble. Same law as every other card: identifier first, then
|
|
517
519
|
// the facts as fields, then his own words as a quote — never as a field, which
|
|
518
520
|
// keeps one line and clipped the name of the very session the card is about.
|
|
521
|
+
//
|
|
522
|
+
// A session has no name, so line 2 says what happened to it. The 36-character
|
|
523
|
+
// id is not printed: he cannot type it, cannot search it and cannot act on it.
|
|
524
|
+
// It is still in the card — inside the `rm` command at the bottom, which is the
|
|
525
|
+
// one place it is of any use.
|
|
519
526
|
const renderSession = (e) => join([
|
|
520
527
|
typeLine(iconFor(e), 'Session', e.action),
|
|
521
528
|
'',
|
|
522
|
-
field('Id', e.id),
|
|
523
529
|
field('Project', e.workdir),
|
|
524
530
|
field('Reason', e.reason),
|
|
525
531
|
e.opened ? '' : null,
|
|
@@ -531,9 +537,12 @@ const renderHeartbeatMiss = (e) => {
|
|
|
531
537
|
const icon = iconFor(e);
|
|
532
538
|
const action = e.recovered ? 'ok' : 'miss';
|
|
533
539
|
return join([
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
540
|
+
// The task's name on the type line, exactly as a job card carries it. The
|
|
541
|
+
// `Task:` row said the same thing a floor below. No sender in any
|
|
542
|
+
// repository builds this event any more — the silence watchdog sends an
|
|
543
|
+
// ordinary job with `--status silent` — but a machine still running the old
|
|
544
|
+
// copy of that watchdog can, and the card it gets must obey the template.
|
|
545
|
+
typeLine(icon, 'Heartbeat', e.job, undefined, action),
|
|
537
546
|
field('Reason', e.note),
|
|
538
547
|
field('Expected', e.expected),
|
|
539
548
|
field(e.recovered ? 'Last run' : 'Last seen', e.lastSeen)
|
package/package.json
CHANGED