@fusion.dev/helix-headless 0.212.2 → 0.214.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/api.json +1 -1
- package/custom-elements.json +1 -1
- package/dist/log/buffer.d.ts +35 -2
- package/dist/log/buffer.js +39 -2
- package/dist/log/index.d.ts +1 -1
- package/dist/log/index.js +1 -0
- package/dist/log/line/log-line.d.ts +34 -3
- package/dist/log/line/log-line.js +41 -5
- package/dist/log/root/log-root.d.ts +27 -3
- package/dist/log/root/log-root.js +24 -8
- package/dist/log/rows.d.ts +4 -2
- package/dist/log/rows.js +11 -7
- package/dist/terminal/keys.d.ts +1 -1
- package/dist/terminal/keys.js +8 -3
- package/dist/terminal/root/terminal-root.d.ts +51 -1
- package/dist/terminal/root/terminal-root.js +101 -3
- package/examples.json +1 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +242 -10
- package/types/vue.d.ts +62 -2
- package/types/words.d.ts +41 -0
package/dist/log/buffer.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { type LogRun } from './ansi.js';
|
|
2
2
|
/** How much a line matters, which a stylesheet draws and a screen reader may hear. */
|
|
3
|
-
export type LogLevel = 'debug' | 'error' | 'info' | 'warn';
|
|
3
|
+
export type LogLevel = 'debug' | 'error' | 'info' | 'log' | 'warn';
|
|
4
|
+
/**
|
|
5
|
+
* The levels from the quietest to the loudest, which `level` on a log counts
|
|
6
|
+
* up from: `log` is a plain line, a console's `console.log`, below `info`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LOG_LEVELS: readonly LogLevel[];
|
|
9
|
+
/** Whether a line at `level` shows under a log whose quietest level shown is `floor`. A line with no level is `info`. */
|
|
10
|
+
export declare const loudEnough: (level: LogLevel | undefined, floor: LogLevel | undefined) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* The words a screen reader hears before a line's text, or nothing where the
|
|
13
|
+
* text already starts with them: `console.error(error)` writes `Error: …`,
|
|
14
|
+
* which `Error:` before it would read twice. The words' closing colon is not
|
|
15
|
+
* needed, and a word that only starts the text's first word, such as `Error`
|
|
16
|
+
* before `Errors`, is still said.
|
|
17
|
+
*/
|
|
18
|
+
export declare const levelSaid: (words: string | undefined, text: string) => string | undefined;
|
|
4
19
|
/** Where a step of the run stands. */
|
|
5
20
|
export type LogStatus = 'done' | 'failed' | 'pending' | 'running' | 'skipped';
|
|
6
21
|
/** One line of output, as `lines` holds it and `parse` returns it. */
|
|
@@ -9,6 +24,11 @@ export interface LogLine {
|
|
|
9
24
|
at?: number | string;
|
|
10
25
|
/** The `id` of the group in `groups` the line belongs to. */
|
|
11
26
|
group?: string;
|
|
27
|
+
/**
|
|
28
|
+
* How many times it was written in a row, where the log folds repeats with
|
|
29
|
+
* `collapse`. Absent is once.
|
|
30
|
+
*/
|
|
31
|
+
count?: number;
|
|
12
32
|
/** How much it matters. Absent is `info`. */
|
|
13
33
|
level?: LogLevel;
|
|
14
34
|
/** What was written, with any ANSI escape codes still in it. */
|
|
@@ -95,6 +115,8 @@ export declare class LogBuffer {
|
|
|
95
115
|
dropped: number;
|
|
96
116
|
/** The most finished lines kept, or 0 to keep them all. */
|
|
97
117
|
limit: number;
|
|
118
|
+
/** Whether a line written again straight after itself folds into it, with a count. */
|
|
119
|
+
collapse: boolean;
|
|
98
120
|
/** Reads each finished line of written text. */
|
|
99
121
|
parse: LogParser | undefined;
|
|
100
122
|
/** The steps `parse` opened, in order, replaced as a whole when one changes. */
|
|
@@ -119,8 +141,13 @@ export declare class LogBuffer {
|
|
|
119
141
|
* lines it finished.
|
|
120
142
|
*/
|
|
121
143
|
write(chunk: string): number;
|
|
122
|
-
/** Replaces every line, starting a fresh buffer that keeps its `parse` and `
|
|
144
|
+
/** Replaces every line, starting a fresh buffer that keeps its `parse`, `limit` and `collapse`. */
|
|
123
145
|
replace(lines: readonly LogLine[]): void;
|
|
146
|
+
/**
|
|
147
|
+
* Adds one finished line as data, after the lines held, folding it where
|
|
148
|
+
* `collapse` says, and lets the oldest go past `limit`.
|
|
149
|
+
*/
|
|
150
|
+
push(line: LogLine): void;
|
|
124
151
|
/** Lets every line go. */
|
|
125
152
|
clear(): void;
|
|
126
153
|
/** The line still being written, or undefined where the last line ended. */
|
|
@@ -142,6 +169,12 @@ export declare class LogBuffer {
|
|
|
142
169
|
* as a CI page folds a step that passed.
|
|
143
170
|
*/
|
|
144
171
|
private closeStep;
|
|
172
|
+
/**
|
|
173
|
+
* Keeps a finished line, or folds it into the line before it where it
|
|
174
|
+
* repeats that line and `collapse` is set. The fold is a new object, so a
|
|
175
|
+
* log comparing lines sees it change, drawn in the runs of the first.
|
|
176
|
+
*/
|
|
177
|
+
private add;
|
|
145
178
|
/** Lets the oldest lines go past `limit`, all at once. */
|
|
146
179
|
private trim;
|
|
147
180
|
}
|
package/dist/log/buffer.js
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import { parseAnsi } from './ansi.js';
|
|
2
|
+
export const LOG_LEVELS = ['debug', 'log', 'info', 'warn', 'error'];
|
|
3
|
+
export const loudEnough = (level, floor) => floor === undefined || LOG_LEVELS.indexOf(level ?? 'info') >= LOG_LEVELS.indexOf(floor);
|
|
4
|
+
export const levelSaid = (words, text) => {
|
|
5
|
+
const word = words?.trim().replace(/:$/, '').toLocaleLowerCase();
|
|
6
|
+
if (!word) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const start = text.trimStart();
|
|
10
|
+
const repeats = start.slice(0, word.length).toLocaleLowerCase() === word &&
|
|
11
|
+
!/^[\p{L}\p{N}]/u.test(start.slice(word.length));
|
|
12
|
+
return repeats ? undefined : words;
|
|
13
|
+
};
|
|
14
|
+
const repeats = (line, last) => last !== undefined &&
|
|
15
|
+
last.text === line.text &&
|
|
16
|
+
last.level === line.level &&
|
|
17
|
+
last.group === line.group;
|
|
2
18
|
export const stepDuration = (seconds) => {
|
|
3
19
|
const whole = Math.max(0, Math.round(seconds));
|
|
4
20
|
if (whole < 60) {
|
|
@@ -23,6 +39,7 @@ export class LogBuffer {
|
|
|
23
39
|
this.lines = [];
|
|
24
40
|
this.dropped = 0;
|
|
25
41
|
this.limit = 0;
|
|
42
|
+
this.collapse = false;
|
|
26
43
|
this.parse = undefined;
|
|
27
44
|
this.steps = [];
|
|
28
45
|
this.openStep = undefined;
|
|
@@ -60,7 +77,10 @@ export class LogBuffer {
|
|
|
60
77
|
return finished;
|
|
61
78
|
}
|
|
62
79
|
replace(lines) {
|
|
63
|
-
this.lines = [
|
|
80
|
+
this.lines = [];
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
this.add(line);
|
|
83
|
+
}
|
|
64
84
|
this.dropped = 0;
|
|
65
85
|
this.partial = '';
|
|
66
86
|
this.returned = false;
|
|
@@ -72,6 +92,10 @@ export class LogBuffer {
|
|
|
72
92
|
this.stepStarted = undefined;
|
|
73
93
|
this.trim();
|
|
74
94
|
}
|
|
95
|
+
push(line) {
|
|
96
|
+
this.add(line);
|
|
97
|
+
this.trim();
|
|
98
|
+
}
|
|
75
99
|
clear() {
|
|
76
100
|
this.replace([]);
|
|
77
101
|
}
|
|
@@ -126,7 +150,7 @@ export class LogBuffer {
|
|
|
126
150
|
this.stepEnded = at ?? this.stepEnded;
|
|
127
151
|
}
|
|
128
152
|
this.runs.set(line, line.text === raw ? flow.runs : parseAnsi(line.text, this.lineStyle).runs);
|
|
129
|
-
this.
|
|
153
|
+
this.add(line);
|
|
130
154
|
}
|
|
131
155
|
}
|
|
132
156
|
this.lineStyle = flow.style;
|
|
@@ -166,6 +190,19 @@ export class LogBuffer {
|
|
|
166
190
|
this.steps = this.steps.map((each, place) => (place === index ? closed : each));
|
|
167
191
|
this.openStep = undefined;
|
|
168
192
|
}
|
|
193
|
+
add(line) {
|
|
194
|
+
const last = this.lines.at(-1);
|
|
195
|
+
if (!this.collapse || !last || !repeats(line, last)) {
|
|
196
|
+
this.lines.push(line);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const folded = { ...last, count: (last.count ?? 1) + (line.count ?? 1) };
|
|
200
|
+
const runs = this.runs.get(last);
|
|
201
|
+
if (runs) {
|
|
202
|
+
this.runs.set(folded, runs);
|
|
203
|
+
}
|
|
204
|
+
this.lines[this.lines.length - 1] = folded;
|
|
205
|
+
}
|
|
169
206
|
trim() {
|
|
170
207
|
const over = this.limit > 0 ? this.lines.length - this.limit : 0;
|
|
171
208
|
if (over > 0) {
|
package/dist/log/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { type LogRun, type LogStyle, parseAnsi, stripAnsi } from './ansi.js';
|
|
2
|
-
export type
|
|
2
|
+
export { type LogGroup, type LogLevel, type LogLine, type LogParsed, type LogParser, type LogStatus, levelSaid, } from './buffer.js';
|
|
3
3
|
export { HelixHeadlessLogGroup } from './group/log-group.js';
|
|
4
4
|
export { HelixHeadlessLogLine, type LogTimes } from './line/log-line.js';
|
|
5
5
|
export { parseGitHubActions, parseVercel } from './parsers.js';
|
package/dist/log/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { parseAnsi, stripAnsi } from './ansi.js';
|
|
2
|
+
export { levelSaid, } from './buffer.js';
|
|
2
3
|
export { HelixHeadlessLogGroup } from './group/log-group.js';
|
|
3
4
|
export { HelixHeadlessLogLine } from './line/log-line.js';
|
|
4
5
|
export { parseGitHubActions, parseVercel } from './parsers.js';
|
|
@@ -13,8 +13,10 @@ export type LogTimes = 'absolute' | 'relative';
|
|
|
13
13
|
* and tells it whether to show its number and its time.
|
|
14
14
|
*
|
|
15
15
|
* `level` says how much a line matters, and the styled tier draws `warn` and
|
|
16
|
-
* `error` lines apart.
|
|
17
|
-
*
|
|
16
|
+
* `error` lines apart. A line at `debug`, `warn` or `error` says its level in
|
|
17
|
+
* words before its text, so a reader moving through the log hears it as well
|
|
18
|
+
* as the color shows it. The text is the line's own content, so you can mark
|
|
19
|
+
* it up yourself.
|
|
18
20
|
*
|
|
19
21
|
* @since 0.200.0
|
|
20
22
|
* @status experimental
|
|
@@ -22,6 +24,9 @@ export type LogTimes = 'absolute' | 'relative';
|
|
|
22
24
|
*
|
|
23
25
|
* @slot - The text of the line.
|
|
24
26
|
*
|
|
27
|
+
* @csspart count - How many times a line written again straight after itself was written, where the log's `collapse` folds repeats. Hidden from screen readers, which hear `count-label`.
|
|
28
|
+
* @csspart count-label - What a screen reader hears for the count, such as "3 times". Visually hidden in the styled tier.
|
|
29
|
+
* @csspart level - What a screen reader hears before the text for a line at `debug`, `warn` or `error`, such as "Warning:". Visually hidden in the styled tier.
|
|
25
30
|
* @csspart line - The row the number, the time, and the text sit in.
|
|
26
31
|
* @csspart line-debug - The row of a line at `debug`.
|
|
27
32
|
* @csspart line-error - The row of a line at `error`.
|
|
@@ -43,7 +48,7 @@ export type LogTimes = 'absolute' | 'relative';
|
|
|
43
48
|
* @cssproperty --log-run-background-color - Written on a `run` with a background, in the same form as `--log-run-color`.
|
|
44
49
|
* @cssproperty --log-run-color - Written on a `run` with a text color: one of the sixteen `--log-ansi-*` colors the log publishes, or the color itself.
|
|
45
50
|
*
|
|
46
|
-
* @cssstate filtered - The log's `query` does not match the line, so it draws nothing.
|
|
51
|
+
* @cssstate filtered - The log's `query` does not match the line, or its `level` is quieter than the log shows, so it draws nothing.
|
|
47
52
|
*/
|
|
48
53
|
export declare class HelixHeadlessLogLine extends HelixElement {
|
|
49
54
|
/** The key this family's words are registered under, its styled tag. */
|
|
@@ -52,8 +57,18 @@ export declare class HelixHeadlessLogLine extends HelixElement {
|
|
|
52
57
|
at: number | string;
|
|
53
58
|
/** How much the line matters. Absent is `info`. */
|
|
54
59
|
level?: LogLevel;
|
|
60
|
+
/**
|
|
61
|
+
* What a screen reader hears before the text of a line at each level, for a
|
|
62
|
+
* page in another language, keyed by level, such as `{ warn: 'Warnung:' }`.
|
|
63
|
+
* A line at `info` or `log` hears nothing extra, and nor does a line whose
|
|
64
|
+
* text already starts with its words, such as `Error: Cannot find module`.
|
|
65
|
+
* As an attribute it is that object as JSON.
|
|
66
|
+
*/
|
|
67
|
+
levelWords: Partial<Record<LogLevel, string>>;
|
|
55
68
|
/** What a screen reader hears for the link on a line's number. `{number}` is the number. */
|
|
56
69
|
lineLabel: string;
|
|
70
|
+
/** @internal Set by the log: how many times the line was written in a row, where `collapse` folds repeats. */
|
|
71
|
+
logCount: number;
|
|
57
72
|
/** @internal Set by the log: whether the query hides the line. */
|
|
58
73
|
logFiltered: boolean;
|
|
59
74
|
/** @internal Set by the log: whether the line belongs to a step. */
|
|
@@ -80,11 +95,23 @@ export declare class HelixHeadlessLogLine extends HelixElement {
|
|
|
80
95
|
logTimes: '' | LogTimes;
|
|
81
96
|
/** @internal Set by the log: the text as styled runs, for a line drawn from data. */
|
|
82
97
|
runs: readonly LogRun[] | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* What a screen reader hears after a line written more than once in a row.
|
|
100
|
+
* `{count}` is how many times, in the locale.
|
|
101
|
+
*/
|
|
102
|
+
timesLabel: string;
|
|
83
103
|
/**
|
|
84
104
|
* @internal Read by the log to find its lines among its children and inside
|
|
85
105
|
* its groups, which is cheaper than `instanceof`.
|
|
86
106
|
*/
|
|
87
107
|
readonly isLogLine = true;
|
|
108
|
+
/**
|
|
109
|
+
* How the text a page wrote starts, so a line whose text already says its
|
|
110
|
+
* level does not hear it twice. Read from the second update on, which a log
|
|
111
|
+
* asks for once it has hydrated, as the server has no children to read and
|
|
112
|
+
* the first render is the server's.
|
|
113
|
+
*/
|
|
114
|
+
private written;
|
|
88
115
|
protected willUpdate(changed: PropertyValues): void;
|
|
89
116
|
/**
|
|
90
117
|
* The number: a link where the log gave `line-href`, a button where the log
|
|
@@ -116,6 +143,10 @@ export declare class HelixHeadlessLogLine extends HelixElement {
|
|
|
116
143
|
* ink.
|
|
117
144
|
*/
|
|
118
145
|
private renderRuns;
|
|
146
|
+
/** The count of a folded line, shown as a number and heard as words. */
|
|
147
|
+
private renderCount;
|
|
148
|
+
/** Reads the new text where the line says a level, which it may already say. */
|
|
149
|
+
private readonly handleSlot;
|
|
119
150
|
protected render(): TemplateResult | typeof nothing;
|
|
120
151
|
}
|
|
121
152
|
declare global {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
|
-
import { customElement, formatWords, HelixElement, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
2
|
+
import { customElement, formatWords, HelixElement, words, wordsConverter, wordsLocale, } from '@fusion.dev/helix-core';
|
|
3
3
|
import { html, nothing } from 'lit';
|
|
4
4
|
import { property } from 'lit/decorators.js';
|
|
5
|
-
import { timeOf } from '../buffer.js';
|
|
5
|
+
import { levelSaid, timeOf } from '../buffer.js';
|
|
6
6
|
import { markMatches } from '../rows.js';
|
|
7
7
|
const NAME = /^[a-z-]+$/;
|
|
8
8
|
const elapsed = (seconds) => {
|
|
@@ -16,7 +16,13 @@ let HelixHeadlessLogLine = class HelixHeadlessLogLine extends HelixElement {
|
|
|
16
16
|
constructor() {
|
|
17
17
|
super(...arguments);
|
|
18
18
|
this.at = '';
|
|
19
|
+
this.levelWords = {
|
|
20
|
+
debug: 'Debug:',
|
|
21
|
+
error: 'Error:',
|
|
22
|
+
warn: 'Warning:',
|
|
23
|
+
};
|
|
19
24
|
this.lineLabel = 'Line {number}';
|
|
25
|
+
this.logCount = 1;
|
|
20
26
|
this.logFiltered = false;
|
|
21
27
|
this.logGrouped = false;
|
|
22
28
|
this.logHref = '';
|
|
@@ -30,12 +36,22 @@ let HelixHeadlessLogLine = class HelixHeadlessLogLine extends HelixElement {
|
|
|
30
36
|
this.logStop = false;
|
|
31
37
|
this.logTimes = '';
|
|
32
38
|
this.runs = undefined;
|
|
39
|
+
this.timesLabel = '{count} times';
|
|
33
40
|
this.isLogLine = true;
|
|
41
|
+
this.written = '';
|
|
42
|
+
this.handleSlot = () => {
|
|
43
|
+
if (this.level !== undefined && this.levelWords[this.level]) {
|
|
44
|
+
this.requestUpdate();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
34
47
|
}
|
|
35
48
|
static { this.words = 'helix-log'; }
|
|
36
49
|
willUpdate(changed) {
|
|
37
50
|
super.willUpdate(changed);
|
|
38
51
|
this.toggleState('filtered', this.logFiltered);
|
|
52
|
+
if (this.hasUpdated && !this.runs) {
|
|
53
|
+
this.written = (this.textContent ?? '').trimStart().slice(0, 40);
|
|
54
|
+
}
|
|
39
55
|
}
|
|
40
56
|
renderNumber() {
|
|
41
57
|
if (!this.logNumbers || this.logNumber <= 0) {
|
|
@@ -83,7 +99,7 @@ let HelixHeadlessLogLine = class HelixHeadlessLogLine extends HelixElement {
|
|
|
83
99
|
return html `<time part="time" datetime=${new Date(time).toISOString()}>${reads}</time>`;
|
|
84
100
|
}
|
|
85
101
|
renderText() {
|
|
86
|
-
return html `<slot>${this.runs ? this.renderRuns() : nothing}</slot>`;
|
|
102
|
+
return html `<slot @slotchange=${this.handleSlot}>${this.runs ? this.renderRuns() : nothing}</slot>`;
|
|
87
103
|
}
|
|
88
104
|
renderRuns() {
|
|
89
105
|
return markMatches(this.runs ?? [], this.logQuery).map((piece) => {
|
|
@@ -106,14 +122,23 @@ let HelixHeadlessLogLine = class HelixHeadlessLogLine extends HelixElement {
|
|
|
106
122
|
return piece.match ? html `<mark part="match">${run}</mark>` : run;
|
|
107
123
|
});
|
|
108
124
|
}
|
|
125
|
+
renderCount() {
|
|
126
|
+
if (this.logCount < 2) {
|
|
127
|
+
return nothing;
|
|
128
|
+
}
|
|
129
|
+
return html ` <span part="count" aria-hidden="true">${this.logCount}</span><span part="count-label"
|
|
130
|
+
>${formatWords(this, 'timesLabel', { count: this.logCount })}</span
|
|
131
|
+
>`;
|
|
132
|
+
}
|
|
109
133
|
render() {
|
|
110
134
|
if (this.logFiltered) {
|
|
111
135
|
return nothing;
|
|
112
136
|
}
|
|
113
137
|
const level = this.level;
|
|
138
|
+
const said = levelSaid(level === undefined ? undefined : this.levelWords[level], this.runs ? this.runs.map((run) => run.text).join('') : this.written);
|
|
114
139
|
return html `<div
|
|
115
|
-
part="line${level ? { debug: ' line-debug', error: ' line-error', info: '', warn: ' line-warn' }[level] : ''}${this.logMarked ? ' line-marked' : ''}${this.logSelected ? ' line-selected' : ''}${this.logGrouped ? ' line-grouped' : ''}"
|
|
116
|
-
>${this.renderNumber()}${this.renderTime()}<span part="text">${this.renderText()}</span
|
|
140
|
+
part="line${level ? { debug: ' line-debug', error: ' line-error', info: '', log: '', warn: ' line-warn' }[level] : ''}${this.logMarked ? ' line-marked' : ''}${this.logSelected ? ' line-selected' : ''}${this.logGrouped ? ' line-grouped' : ''}"
|
|
141
|
+
>${this.renderNumber()}${this.renderTime()}${said ? html `<span part="level">${said} </span>` : nothing}<span part="text">${this.renderText()}</span>${this.renderCount()}</div>`;
|
|
117
142
|
}
|
|
118
143
|
};
|
|
119
144
|
__decorate([
|
|
@@ -122,10 +147,17 @@ __decorate([
|
|
|
122
147
|
__decorate([
|
|
123
148
|
property({ reflect: true })
|
|
124
149
|
], HelixHeadlessLogLine.prototype, "level", void 0);
|
|
150
|
+
__decorate([
|
|
151
|
+
words,
|
|
152
|
+
property({ attribute: 'level-words', converter: wordsConverter })
|
|
153
|
+
], HelixHeadlessLogLine.prototype, "levelWords", void 0);
|
|
125
154
|
__decorate([
|
|
126
155
|
words,
|
|
127
156
|
property({ attribute: 'line-label' })
|
|
128
157
|
], HelixHeadlessLogLine.prototype, "lineLabel", void 0);
|
|
158
|
+
__decorate([
|
|
159
|
+
property({ attribute: false })
|
|
160
|
+
], HelixHeadlessLogLine.prototype, "logCount", void 0);
|
|
129
161
|
__decorate([
|
|
130
162
|
property({ attribute: false })
|
|
131
163
|
], HelixHeadlessLogLine.prototype, "logFiltered", void 0);
|
|
@@ -165,6 +197,10 @@ __decorate([
|
|
|
165
197
|
__decorate([
|
|
166
198
|
property({ attribute: false })
|
|
167
199
|
], HelixHeadlessLogLine.prototype, "runs", void 0);
|
|
200
|
+
__decorate([
|
|
201
|
+
words,
|
|
202
|
+
property({ attribute: 'times-label' })
|
|
203
|
+
], HelixHeadlessLogLine.prototype, "timesLabel", void 0);
|
|
168
204
|
HelixHeadlessLogLine = __decorate([
|
|
169
205
|
customElement('helix-headless-log-line')
|
|
170
206
|
], HelixHeadlessLogLine);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
-
import { type LogGroup, type LogLine, type LogParser } from '../buffer.js';
|
|
3
|
+
import { type LogGroup, type LogLevel, type LogLine, type LogParser } from '../buffer.js';
|
|
4
4
|
import type { LogTimes } from '../line/log-line.js';
|
|
5
5
|
/** Text a log reads as it arrives: a stream, or anything `for await` reads. */
|
|
6
6
|
export type LogSource = AsyncIterable<string | Uint8Array> | ReadableStream<string | Uint8Array>;
|
|
@@ -117,9 +117,12 @@ declare const HelixHeadlessLog_base: (new (...args: any[]) => import("@fusion.de
|
|
|
117
117
|
* @csspart group-summary-running - The button of a step drawn from `groups` that is `running`.
|
|
118
118
|
* @csspart group-summary-skipped - The button of a step drawn from `groups` that was `skipped`.
|
|
119
119
|
* @csspart line - The row of a line drawn from data.
|
|
120
|
+
* @csspart line-count - How many times a folded line was written, under `collapse`. Hidden from screen readers, which hear `line-count-label`.
|
|
121
|
+
* @csspart line-count-label - What a screen reader hears for a folded line's count, such as "3 times". Visually hidden in the styled tier.
|
|
120
122
|
* @csspart line-debug - The row of a line drawn from data at `debug`.
|
|
121
123
|
* @csspart line-error - The row of a line drawn from data at `error`.
|
|
122
124
|
* @csspart line-grouped - The row of a line drawn from data that belongs to a step.
|
|
125
|
+
* @csspart line-level - What a screen reader hears before the text of a line drawn from data at `debug`, `warn` or `error`. Visually hidden in the styled tier.
|
|
123
126
|
* @csspart line-marked - The row of a line drawn from data that `mark-lines` names.
|
|
124
127
|
* @csspart line-match - Text in a line drawn from data that matches `query`.
|
|
125
128
|
* @csspart line-number - The number of a line drawn from data.
|
|
@@ -164,7 +167,7 @@ declare const HelixHeadlessLog_base: (new (...args: any[]) => import("@fusion.de
|
|
|
164
167
|
*
|
|
165
168
|
* @cssstate done - The run is over: `done` is set, or `source` ended.
|
|
166
169
|
* @cssstate empty - The log has no lines.
|
|
167
|
-
* @cssstate filtered - `query` is set, so only
|
|
170
|
+
* @cssstate filtered - `query` or `level` is set, so only the lines they keep are shown.
|
|
168
171
|
* @cssstate pinned - The log follows its newest line.
|
|
169
172
|
*/
|
|
170
173
|
export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
@@ -176,6 +179,13 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
176
179
|
static words: string;
|
|
177
180
|
/** How tall a row is assumed to be before one has been drawn, in pixels. */
|
|
178
181
|
static readonly ESTIMATE = 20;
|
|
182
|
+
/**
|
|
183
|
+
* Folds a line written again straight after itself into it, with a count,
|
|
184
|
+
* as a browser's console does, so a loop writing one warning a thousand
|
|
185
|
+
* times is one line. For lines written or given as data, read as they
|
|
186
|
+
* arrive; numbers, `line-href` and the selection count a folded line once.
|
|
187
|
+
*/
|
|
188
|
+
collapse: boolean;
|
|
179
189
|
/**
|
|
180
190
|
* Whether the log opens following its newest line, when `pinned` is not set.
|
|
181
191
|
* `default-pinned="false"` opens it where it starts.
|
|
@@ -202,6 +212,7 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
202
212
|
errorLabel: string;
|
|
203
213
|
/** The log's name, read by screen readers, where `label` is empty. */
|
|
204
214
|
fallbackLabel: string;
|
|
215
|
+
set format(next: 'github-actions' | 'vercel' | undefined);
|
|
205
216
|
/**
|
|
206
217
|
* The format written text is in, read with a parser Helix ships:
|
|
207
218
|
* `github-actions` reads a GitHub Actions log's workflow commands into steps
|
|
@@ -209,7 +220,6 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
209
220
|
* reads none. `parse` wins over it.
|
|
210
221
|
*/
|
|
211
222
|
get format(): 'github-actions' | 'vercel' | undefined;
|
|
212
|
-
set format(next: 'github-actions' | 'vercel' | undefined);
|
|
213
223
|
/**
|
|
214
224
|
* What a screen reader hears when a step finishes or fails. `{heading}` is
|
|
215
225
|
* the step's heading and `{status}` is `done` or `failed`.
|
|
@@ -225,6 +235,13 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
225
235
|
groups: readonly LogGroup[];
|
|
226
236
|
/** The log's name, read by screen readers. */
|
|
227
237
|
label: string;
|
|
238
|
+
/**
|
|
239
|
+
* The quietest level shown: `info` hides `debug` and `log` lines, `warn`
|
|
240
|
+
* shows warnings and errors, and `error` errors only. Unset shows every line.
|
|
241
|
+
* Lines it hides are kept, so lowering it shows them again, and a step shows
|
|
242
|
+
* while it holds a line that shows.
|
|
243
|
+
*/
|
|
244
|
+
level?: LogLevel;
|
|
228
245
|
/**
|
|
229
246
|
* The most lines kept, or 0 to keep them all. Past it the oldest lines are
|
|
230
247
|
* let go, and the line above the log says how many.
|
|
@@ -382,6 +399,13 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
382
399
|
* after the last one is the line still being written.
|
|
383
400
|
*/
|
|
384
401
|
write(chunk: string): void;
|
|
402
|
+
/**
|
|
403
|
+
* Adds one line as data, after the lines the log holds: its text, and its
|
|
404
|
+
* level, time and group where it has them. For lines that arrive one at a
|
|
405
|
+
* time with a level of their own, such as a console's, where `write` takes
|
|
406
|
+
* text.
|
|
407
|
+
*/
|
|
408
|
+
writeLine(line: LogLine): void;
|
|
385
409
|
/**
|
|
386
410
|
* Lets every line go, and forgets which groups the reader opened, keeping
|
|
387
411
|
* `groups`, `parse`, and `limit`.
|
|
@@ -9,7 +9,7 @@ import { repeat } from 'lit/directives/repeat.js';
|
|
|
9
9
|
import { html } from 'lit/static-html.js';
|
|
10
10
|
import { lineRuns } from '../../code-block/lines.js';
|
|
11
11
|
import { stripAnsi } from '../ansi.js';
|
|
12
|
-
import { LogBuffer, timeOf, } from '../buffer.js';
|
|
12
|
+
import { LogBuffer, loudEnough, timeOf, } from '../buffer.js';
|
|
13
13
|
import { LOG_FORMATS } from '../parsers.js';
|
|
14
14
|
import { logRows } from '../rows.js';
|
|
15
15
|
const STATUS_NAMES = {
|
|
@@ -24,6 +24,7 @@ const inRuns = (runs, number) => runs.some(([first, last]) => number >= first &&
|
|
|
24
24
|
let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixElement) {
|
|
25
25
|
constructor() {
|
|
26
26
|
super(...arguments);
|
|
27
|
+
this.collapse = false;
|
|
27
28
|
this.defaultPinned = true;
|
|
28
29
|
this.doneLabel = 'Finished';
|
|
29
30
|
this.droppedLabel = '{count, plural, one {# earlier line is} other {# earlier lines are}} not kept';
|
|
@@ -199,15 +200,15 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
199
200
|
this.doneState = next;
|
|
200
201
|
this.requestUpdate('done', previous);
|
|
201
202
|
}
|
|
202
|
-
get format() {
|
|
203
|
-
return this.formatValue;
|
|
204
|
-
}
|
|
205
203
|
set format(next) {
|
|
206
204
|
const previous = this.formatValue;
|
|
207
205
|
this.formatValue = next;
|
|
208
206
|
this.buffer.parse = this.parseValue ?? this.formatParser;
|
|
209
207
|
this.requestUpdate('format', previous);
|
|
210
208
|
}
|
|
209
|
+
get format() {
|
|
210
|
+
return this.formatValue;
|
|
211
|
+
}
|
|
211
212
|
get lines() {
|
|
212
213
|
return this.buffer.lines;
|
|
213
214
|
}
|
|
@@ -292,6 +293,10 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
292
293
|
this.buffer.write(chunk);
|
|
293
294
|
this.requestUpdate();
|
|
294
295
|
}
|
|
296
|
+
writeLine(line) {
|
|
297
|
+
this.buffer.push(line);
|
|
298
|
+
this.requestUpdate();
|
|
299
|
+
}
|
|
295
300
|
clear() {
|
|
296
301
|
this.buffer.clear();
|
|
297
302
|
this.opened.clear();
|
|
@@ -519,7 +524,8 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
519
524
|
const start = this.childLines.map((line) => timeOf(line.at)).find((time) => time !== undefined);
|
|
520
525
|
const stop = this.interactive ? this.stopAmong(this.reachable()) : 0;
|
|
521
526
|
this.childLines.forEach((line, at) => {
|
|
522
|
-
const filtered = needle !== '' && !(line.textContent ?? '').toLowerCase().includes(needle)
|
|
527
|
+
const filtered = (needle !== '' && !(line.textContent ?? '').toLowerCase().includes(needle)) ||
|
|
528
|
+
!loudEnough(line.level, this.level);
|
|
523
529
|
whenHydrated(line, () => {
|
|
524
530
|
line.logNumber = at + 1;
|
|
525
531
|
line.logNumbers = this.lineNumbers;
|
|
@@ -533,7 +539,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
533
539
|
line.logStop = at + 1 === stop;
|
|
534
540
|
line.logGrouped = this.childGroups.some((group) => group.contains(line));
|
|
535
541
|
});
|
|
536
|
-
if (!filtered && needle !== '') {
|
|
542
|
+
if (!filtered && (needle !== '' || this.level !== undefined)) {
|
|
537
543
|
for (const group of this.childGroups) {
|
|
538
544
|
if (!group.open && group.contains(line)) {
|
|
539
545
|
whenHydrated(group, () => {
|
|
@@ -719,6 +725,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
719
725
|
this.lastGroups = this.allGroups;
|
|
720
726
|
if (changed.has('limit')) {
|
|
721
727
|
this.buffer.limit = Math.max(0, Math.floor(this.limit || 0));
|
|
728
|
+
this.buffer.collapse = this.collapse;
|
|
722
729
|
this.buffer.write('');
|
|
723
730
|
}
|
|
724
731
|
if (!this.fed) {
|
|
@@ -742,6 +749,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
742
749
|
this.view = logRows({
|
|
743
750
|
current: this.buffer.current(),
|
|
744
751
|
groups: this.allGroups,
|
|
752
|
+
level: this.level,
|
|
745
753
|
lines: this.buffer.lines,
|
|
746
754
|
open: (id) => this.isOpen(id),
|
|
747
755
|
query: this.query,
|
|
@@ -753,7 +761,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
753
761
|
}
|
|
754
762
|
this.toggleState('pinned', this.pinned);
|
|
755
763
|
this.toggleState('done', this.done);
|
|
756
|
-
this.toggleState('filtered', this.query.trim() !== '');
|
|
764
|
+
this.toggleState('filtered', this.query.trim() !== '' || this.level !== undefined);
|
|
757
765
|
this.toggleState('empty', this.childrenKnown && !this.fed && this.childLines.length === 0);
|
|
758
766
|
}
|
|
759
767
|
updated(changed) {
|
|
@@ -798,7 +806,8 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
798
806
|
const line = this.tag('log-line');
|
|
799
807
|
const { at, level } = row.line;
|
|
800
808
|
return html `<${line}
|
|
801
|
-
exportparts="line, line-debug, line-error, line-grouped, line-marked, line-selected, line-warn,
|
|
809
|
+
exportparts="line, line-debug, line-error, line-grouped, line-marked, line-selected, line-warn, count: line-count,
|
|
810
|
+
count-label: line-count-label, level: line-level, match: line-match,
|
|
802
811
|
number: line-number, run: line-run, run-bold: line-run-bold, run-dim: line-run-dim,
|
|
803
812
|
run-italic: line-run-italic, run-strike: line-run-strike, run-underline: line-run-underline,
|
|
804
813
|
text: line-text, time: line-time"
|
|
@@ -813,6 +822,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
813
822
|
.logSelectable=${this.selectable}
|
|
814
823
|
.logSelected=${this.selected(row.number)}
|
|
815
824
|
.logStop=${row.number === stop}
|
|
825
|
+
.logCount=${row.line.count ?? 1}
|
|
816
826
|
.logGrouped=${row.line.group !== undefined}
|
|
817
827
|
.logStart=${start}
|
|
818
828
|
.logTimes=${this.timestamps ?? ''}
|
|
@@ -884,6 +894,9 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
884
894
|
</div>`;
|
|
885
895
|
}
|
|
886
896
|
};
|
|
897
|
+
__decorate([
|
|
898
|
+
property({ type: Boolean })
|
|
899
|
+
], HelixHeadlessLog.prototype, "collapse", void 0);
|
|
887
900
|
__decorate([
|
|
888
901
|
property({ attribute: 'default-pinned', converter: onUnlessFalse })
|
|
889
902
|
], HelixHeadlessLog.prototype, "defaultPinned", void 0);
|
|
@@ -923,6 +936,9 @@ __decorate([
|
|
|
923
936
|
__decorate([
|
|
924
937
|
property()
|
|
925
938
|
], HelixHeadlessLog.prototype, "label", void 0);
|
|
939
|
+
__decorate([
|
|
940
|
+
property()
|
|
941
|
+
], HelixHeadlessLog.prototype, "level", void 0);
|
|
926
942
|
__decorate([
|
|
927
943
|
property({ converter: numberConverter('limit', 0) })
|
|
928
944
|
], HelixHeadlessLog.prototype, "limit", void 0);
|
package/dist/log/rows.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type LogRun } from './ansi.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type LogGroup, type LogLevel, type LogLine } from './buffer.js';
|
|
3
3
|
/** One row of a log drawn from data: a line, or the heading of a group. */
|
|
4
4
|
export type LogRow = {
|
|
5
5
|
/** The group, as `groups` holds it. */
|
|
@@ -27,6 +27,8 @@ export interface LogRowsInput {
|
|
|
27
27
|
groups: readonly LogGroup[];
|
|
28
28
|
/** The finished lines, oldest first. */
|
|
29
29
|
lines: readonly LogLine[];
|
|
30
|
+
/** The quietest level shown, or undefined for every level. */
|
|
31
|
+
level?: LogLevel | undefined;
|
|
30
32
|
/** Whether a group shows its lines. */
|
|
31
33
|
open: (id: string) => boolean;
|
|
32
34
|
/** Text a line has to hold to be shown, ignoring case. Empty shows every line. */
|
|
@@ -47,7 +49,7 @@ export interface LogRowsInput {
|
|
|
47
49
|
* where it is rather than hidden by the fold. A group with no match is left
|
|
48
50
|
* out, heading and all.
|
|
49
51
|
*/
|
|
50
|
-
export declare const logRows: ({ current, groups, lines, open, query, start }: LogRowsInput) => LogRows;
|
|
52
|
+
export declare const logRows: ({ current, groups, level, lines, open, query, start, }: LogRowsInput) => LogRows;
|
|
51
53
|
/** A run, and whether it is part of a match. */
|
|
52
54
|
export interface LogPiece extends LogRun {
|
|
53
55
|
/** Whether the text matches the search. */
|
package/dist/log/rows.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { stripAnsi } from './ansi.js';
|
|
2
|
+
import { loudEnough } from './buffer.js';
|
|
2
3
|
const plain = new WeakMap();
|
|
3
4
|
const holds = (line, needle) => {
|
|
4
5
|
if (needle === '') {
|
|
@@ -11,12 +12,15 @@ const holds = (line, needle) => {
|
|
|
11
12
|
}
|
|
12
13
|
return text.includes(needle);
|
|
13
14
|
};
|
|
14
|
-
export const logRows = ({ current, groups, lines, open, query, start }) => {
|
|
15
|
+
export const logRows = ({ current, groups, level, lines, open, query, start, }) => {
|
|
15
16
|
const needle = query.trim().toLowerCase();
|
|
16
17
|
const byId = new Map(groups.map((group) => [group.id, group]));
|
|
17
18
|
const all = current ? [...lines, current] : lines;
|
|
18
19
|
const hits = new Set();
|
|
19
|
-
const
|
|
20
|
+
const filtering = needle !== '' || level !== undefined;
|
|
21
|
+
const matches = filtering
|
|
22
|
+
? all.map((line) => holds(line, needle) && loudEnough(line.level, level))
|
|
23
|
+
: undefined;
|
|
20
24
|
let matched = 0;
|
|
21
25
|
if (matches) {
|
|
22
26
|
all.forEach((line, index) => {
|
|
@@ -35,22 +39,22 @@ export const logRows = ({ current, groups, lines, open, query, start }) => {
|
|
|
35
39
|
const group = line.group === undefined ? undefined : byId.get(line.group);
|
|
36
40
|
if (group && !placed.has(group.id)) {
|
|
37
41
|
placed.add(group.id);
|
|
38
|
-
if (
|
|
39
|
-
rows.push({ group, kind: 'group', open:
|
|
42
|
+
if (!filtering || hits.has(group.id)) {
|
|
43
|
+
rows.push({ group, kind: 'group', open: filtering || open(group.id) });
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
|
-
const shown = group === undefined ||
|
|
46
|
+
const shown = group === undefined || filtering || open(group.id);
|
|
43
47
|
if (shown && (matches?.[index] ?? true)) {
|
|
44
48
|
rows.push({ kind: 'line', line, number: start + index + 1 });
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
|
-
if (
|
|
51
|
+
if (!filtering) {
|
|
48
52
|
const last = groups.findLastIndex((group) => placed.has(group.id));
|
|
49
53
|
for (const group of groups.slice(last + 1)) {
|
|
50
54
|
rows.push({ group, kind: 'group', open: open(group.id) });
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
|
-
return { matched:
|
|
57
|
+
return { matched: filtering ? matched : all.length, rows };
|
|
54
58
|
};
|
|
55
59
|
export const markMatches = (runs, query) => {
|
|
56
60
|
const needle = query.trim().toLowerCase();
|
package/dist/terminal/keys.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ChordPress } from '@fusion.dev/helix-core';
|
|
2
2
|
/** What a terminal frame does with a key press before the program sees it. */
|
|
3
|
-
export type TerminalShortcut = 'copy' | 'find' | 'tab-focus';
|
|
3
|
+
export type TerminalShortcut = 'copy' | 'find' | 'paste' | 'tab-focus';
|
|
4
4
|
/** The chord for a shortcut on this keyboard, or `undefined` where the frame takes none. */
|
|
5
5
|
export declare const chordOf: (shortcut: TerminalShortcut, apple: boolean) => string | undefined;
|
|
6
6
|
/** Which shortcut a press is, or `null` for a press the program should have. */
|