orcareplay 0.1.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/api.d.ts +127 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +159 -0
- package/dist/api.js.map +1 -0
- package/dist/args.d.ts +24 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +124 -0
- package/dist/args.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/compare.d.ts +27 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +182 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +358 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/gc.d.ts +24 -0
- package/dist/commands/gc.d.ts.map +1 -0
- package/dist/commands/gc.js +326 -0
- package/dist/commands/gc.js.map +1 -0
- package/dist/commands/inspect.d.ts +24 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +262 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/record.d.ts +17 -0
- package/dist/commands/record.d.ts.map +1 -0
- package/dist/commands/record.js +497 -0
- package/dist/commands/record.js.map +1 -0
- package/dist/commands/replay.d.ts +43 -0
- package/dist/commands/replay.d.ts.map +1 -0
- package/dist/commands/replay.js +800 -0
- package/dist/commands/replay.js.map +1 -0
- package/dist/commands/scrub.d.ts +34 -0
- package/dist/commands/scrub.d.ts.map +1 -0
- package/dist/commands/scrub.js +452 -0
- package/dist/commands/scrub.js.map +1 -0
- package/dist/commands/setup.d.ts +23 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +177 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/config.d.ts +80 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +117 -0
- package/dist/config.js.map +1 -0
- package/dist/exchange-events.d.ts +72 -0
- package/dist/exchange-events.d.ts.map +1 -0
- package/dist/exchange-events.js +170 -0
- package/dist/exchange-events.js.map +1 -0
- package/dist/flags.d.ts +10 -0
- package/dist/flags.d.ts.map +1 -0
- package/dist/flags.js +83 -0
- package/dist/flags.js.map +1 -0
- package/dist/fs-events.d.ts +19 -0
- package/dist/fs-events.d.ts.map +1 -0
- package/dist/fs-events.js +53 -0
- package/dist/fs-events.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +282 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp-server.d.ts +44 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +245 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp.d.ts +111 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +221 -0
- package/dist/mcp.js.map +1 -0
- package/dist/out.d.ts +69 -0
- package/dist/out.d.ts.map +1 -0
- package/dist/out.js +138 -0
- package/dist/out.js.map +1 -0
- package/dist/rasterize.d.ts +62 -0
- package/dist/rasterize.d.ts.map +1 -0
- package/dist/rasterize.js +154 -0
- package/dist/rasterize.js.map +1 -0
- package/dist/serial.d.ts +19 -0
- package/dist/serial.d.ts.map +1 -0
- package/dist/serial.js +34 -0
- package/dist/serial.js.map +1 -0
- package/dist/share-card.d.ts +73 -0
- package/dist/share-card.d.ts.map +1 -0
- package/dist/share-card.js +413 -0
- package/dist/share-card.js.map +1 -0
- package/dist/snapshot.d.ts +30 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +43 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/tls-capture.d.ts +57 -0
- package/dist/tls-capture.d.ts.map +1 -0
- package/dist/tls-capture.js +198 -0
- package/dist/tls-capture.js.map +1 -0
- package/dist/upstream.d.ts +20 -0
- package/dist/upstream.d.ts.map +1 -0
- package/dist/upstream.js +31 -0
- package/dist/upstream.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +49 -0
package/dist/out.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal output.
|
|
3
|
+
*
|
|
4
|
+
* The CLI is most of this product's surface, and most of its output ends up somewhere that is not
|
|
5
|
+
* a terminal — a CI log, a pipe into grep, a pasted GitHub issue. So: colour only on a real TTY,
|
|
6
|
+
* no animation that corrupts a log, one greppable `level event key=value` line per fact, and
|
|
7
|
+
* errors that say what happened, what it means, and what to run next.
|
|
8
|
+
*/
|
|
9
|
+
export declare function stripAnsi(s: string): string;
|
|
10
|
+
export type Scalar = string | number | boolean | null | undefined;
|
|
11
|
+
export type Fields = Record<string, Scalar>;
|
|
12
|
+
/**
|
|
13
|
+
* One structured fact, before it is turned into a line of text.
|
|
14
|
+
*
|
|
15
|
+
* The same record that becomes `warn capture.empty exchanges=0 …` on a terminal. A caller that
|
|
16
|
+
* embedded orca has no stdout to read, and an agent asking for `--json` should not be handed
|
|
17
|
+
* prose to parse — so every fact is available as data at the point it is emitted, and the line is
|
|
18
|
+
* a rendering of it rather than the only form it ever takes.
|
|
19
|
+
*
|
|
20
|
+
* Fields arrive redacted, exactly as the terminal would show them: a key must not reach a JSON
|
|
21
|
+
* consumer just because it took a different route out.
|
|
22
|
+
*/
|
|
23
|
+
export interface LogEntry {
|
|
24
|
+
level: 'info' | 'warn' | 'error' | 'debug';
|
|
25
|
+
event: string;
|
|
26
|
+
fields: Record<string, Scalar>;
|
|
27
|
+
}
|
|
28
|
+
export interface OutputOptions {
|
|
29
|
+
write: (s: string) => void;
|
|
30
|
+
/** Receives every fact as data, in the order it was emitted. */
|
|
31
|
+
sink?: (entry: LogEntry) => void;
|
|
32
|
+
isTTY?: boolean;
|
|
33
|
+
env?: Record<string, string | undefined>;
|
|
34
|
+
verbose?: boolean;
|
|
35
|
+
ci?: boolean;
|
|
36
|
+
/** Explicit override from `--no-color`. Wins over TTY detection and NO_COLOR alike. */
|
|
37
|
+
color?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface Failure {
|
|
40
|
+
event: string;
|
|
41
|
+
/** What happened. */
|
|
42
|
+
what: string;
|
|
43
|
+
/** What it means — the part that turns a message into an explanation. */
|
|
44
|
+
why?: string;
|
|
45
|
+
/** The next command to run. An error without this is a bug report we will receive instead. */
|
|
46
|
+
next?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare class Output {
|
|
49
|
+
#private;
|
|
50
|
+
constructor(opts: OutputOptions);
|
|
51
|
+
get isVerbose(): boolean;
|
|
52
|
+
info(event: string, fields?: Fields): void;
|
|
53
|
+
warn(event: string, fields?: Fields): void;
|
|
54
|
+
error(event: string, fields?: Fields): void;
|
|
55
|
+
debug(event: string, fields?: Fields): void;
|
|
56
|
+
/** One line per phase. Always printed, including under --ci. */
|
|
57
|
+
phase(event: string, fields?: Fields): void;
|
|
58
|
+
/**
|
|
59
|
+
* Transient status. Printed only on an interactive TTY: in a pipe it is noise, and in CI it
|
|
60
|
+
* turns a log into thousands of lines of escape codes.
|
|
61
|
+
*/
|
|
62
|
+
progress(message: string): void;
|
|
63
|
+
/** Free-form text for help and reports, where key=value would be worse than prose. */
|
|
64
|
+
plain(message?: string): void;
|
|
65
|
+
failure(f: Failure): void;
|
|
66
|
+
/** Aligned columns, no box drawing — a table people can pipe into awk. */
|
|
67
|
+
table(headers: string[], rows: string[][]): void;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=out.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"out.d.ts","sourceRoot":"","sources":["../src/out.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAcH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AAkBD,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAClE,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,gEAAgE;IAChE,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,uFAAuF;IACvF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8FAA8F;IAC9F,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,MAAM;;gBAQL,IAAI,EAAE,aAAa;IAa/B,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI;IAI9C,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI;IAI9C,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI;IAI/C,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI;IAK/C,gEAAgE;IAChE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI;IAI/C;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK/B,sFAAsF;IACtF,KAAK,CAAC,OAAO,SAAK,GAAG,IAAI;IAIzB,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAOzB,0EAA0E;IAC1E,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI;CAiCjD"}
|
package/dist/out.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal output.
|
|
3
|
+
*
|
|
4
|
+
* The CLI is most of this product's surface, and most of its output ends up somewhere that is not
|
|
5
|
+
* a terminal — a CI log, a pipe into grep, a pasted GitHub issue. So: colour only on a real TTY,
|
|
6
|
+
* no animation that corrupts a log, one greppable `level event key=value` line per fact, and
|
|
7
|
+
* errors that say what happened, what it means, and what to run next.
|
|
8
|
+
*/
|
|
9
|
+
const ESC = String.fromCharCode(27);
|
|
10
|
+
const CSI = `${ESC}[`;
|
|
11
|
+
const STYLE = {
|
|
12
|
+
dim: `${CSI}2m`,
|
|
13
|
+
bold: `${CSI}1m`,
|
|
14
|
+
reset: `${CSI}0m`,
|
|
15
|
+
};
|
|
16
|
+
/** Matches SGR sequences without embedding a raw control character in source. */
|
|
17
|
+
const ANSI_RE = new RegExp(`${ESC}\\[[0-9;]*m`, 'g');
|
|
18
|
+
export function stripAnsi(s) {
|
|
19
|
+
return s.replace(ANSI_RE, '');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Shapes that must never reach a terminal. §7 applies to output, not only to disk: terminals
|
|
23
|
+
* scroll into screenshots, and a key printed once is a key leaked forever.
|
|
24
|
+
*/
|
|
25
|
+
const SECRET_PATTERNS = [
|
|
26
|
+
/\bsk-[A-Za-z0-9_-]{16,}/,
|
|
27
|
+
/\bgh[pousr]_[A-Za-z0-9]{20,}/,
|
|
28
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
29
|
+
/\bxox[baprs]-[A-Za-z0-9-]{10,}/,
|
|
30
|
+
/\bAIza[0-9A-Za-z_-]{35}\b/,
|
|
31
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
|
|
32
|
+
];
|
|
33
|
+
/** Keys whose value is secret by name, whatever it happens to look like. */
|
|
34
|
+
const SECRET_KEYS = /^(api[_-]?key|key|token|secret|password|authorization|cookie)$/i;
|
|
35
|
+
export class Output {
|
|
36
|
+
#write;
|
|
37
|
+
#color;
|
|
38
|
+
#tty;
|
|
39
|
+
#verbose;
|
|
40
|
+
#ci;
|
|
41
|
+
#sink;
|
|
42
|
+
constructor(opts) {
|
|
43
|
+
const env = opts.env ?? {};
|
|
44
|
+
this.#write = opts.write;
|
|
45
|
+
this.#sink = opts.sink;
|
|
46
|
+
this.#tty = opts.isTTY ?? false;
|
|
47
|
+
this.#ci = opts.ci ?? false;
|
|
48
|
+
this.#verbose = opts.verbose ?? false;
|
|
49
|
+
// NO_COLOR is honoured whatever its value; see no-color.org. An explicit `color: false` from
|
|
50
|
+
// `--no-color` overrides everything — it is the flag people reach for when a log has come out
|
|
51
|
+
// unreadable, so it has to win rather than be one input among several.
|
|
52
|
+
this.#color = opts.color ?? (this.#tty && !this.#ci && env.NO_COLOR === undefined);
|
|
53
|
+
}
|
|
54
|
+
get isVerbose() {
|
|
55
|
+
return this.#verbose;
|
|
56
|
+
}
|
|
57
|
+
info(event, fields = {}) {
|
|
58
|
+
this.#line('info', event, fields);
|
|
59
|
+
}
|
|
60
|
+
warn(event, fields = {}) {
|
|
61
|
+
this.#line('warn', event, fields, STYLE.bold);
|
|
62
|
+
}
|
|
63
|
+
error(event, fields = {}) {
|
|
64
|
+
this.#line('error', event, fields, STYLE.bold);
|
|
65
|
+
}
|
|
66
|
+
debug(event, fields = {}) {
|
|
67
|
+
if (!this.#verbose)
|
|
68
|
+
return;
|
|
69
|
+
this.#line('debug', event, fields, STYLE.dim);
|
|
70
|
+
}
|
|
71
|
+
/** One line per phase. Always printed, including under --ci. */
|
|
72
|
+
phase(event, fields = {}) {
|
|
73
|
+
this.#line('info', event, fields);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Transient status. Printed only on an interactive TTY: in a pipe it is noise, and in CI it
|
|
77
|
+
* turns a log into thousands of lines of escape codes.
|
|
78
|
+
*/
|
|
79
|
+
progress(message) {
|
|
80
|
+
if (!this.#tty || this.#ci)
|
|
81
|
+
return;
|
|
82
|
+
this.#write(`${this.#paint(STYLE.dim)}${message}${this.#paint(STYLE.reset)}\n`);
|
|
83
|
+
}
|
|
84
|
+
/** Free-form text for help and reports, where key=value would be worse than prose. */
|
|
85
|
+
plain(message = '') {
|
|
86
|
+
this.#write(`${message}\n`);
|
|
87
|
+
}
|
|
88
|
+
failure(f) {
|
|
89
|
+
this.error(f.event, {});
|
|
90
|
+
this.plain(` ${f.what}`);
|
|
91
|
+
if (f.why)
|
|
92
|
+
this.plain(` ${f.why}`);
|
|
93
|
+
if (f.next)
|
|
94
|
+
this.plain(` next: ${f.next}`);
|
|
95
|
+
}
|
|
96
|
+
/** Aligned columns, no box drawing — a table people can pipe into awk. */
|
|
97
|
+
table(headers, rows) {
|
|
98
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? '').length)));
|
|
99
|
+
const render = (cells) => cells
|
|
100
|
+
.map((c, i) => (i === cells.length - 1 ? c : c.padEnd(widths[i] ?? 0)))
|
|
101
|
+
.join(' ')
|
|
102
|
+
.trimEnd();
|
|
103
|
+
this.#write(`${this.#paint(STYLE.dim)}${render(headers)}${this.#paint(STYLE.reset)}\n`);
|
|
104
|
+
for (const row of rows)
|
|
105
|
+
this.#write(`${render(row)}\n`);
|
|
106
|
+
}
|
|
107
|
+
#paint(code) {
|
|
108
|
+
return this.#color ? code : '';
|
|
109
|
+
}
|
|
110
|
+
#line(level, event, fields, style) {
|
|
111
|
+
const parts = [level, event];
|
|
112
|
+
const structured = {};
|
|
113
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
114
|
+
if (v === undefined || v === null)
|
|
115
|
+
continue;
|
|
116
|
+
// Redacted once, for both destinations. A secret must not reach a JSON consumer merely
|
|
117
|
+
// because it left by a different door than the terminal.
|
|
118
|
+
structured[k] = redactValue(k, v);
|
|
119
|
+
parts.push(`${k}=${formatValue(k, v)}`);
|
|
120
|
+
}
|
|
121
|
+
this.#sink?.({ level: level, event, fields: structured });
|
|
122
|
+
const body = parts.join(' ');
|
|
123
|
+
this.#write(`${style ? this.#paint(style) : ''}${body}${style ? this.#paint(STYLE.reset) : ''}\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function isSecret(key, raw) {
|
|
127
|
+
return SECRET_KEYS.test(key) || SECRET_PATTERNS.some((re) => re.test(raw));
|
|
128
|
+
}
|
|
129
|
+
/** The value as data — the number or boolean kept as itself, anything secret replaced. */
|
|
130
|
+
function redactValue(key, value) {
|
|
131
|
+
return isSecret(key, String(value)) ? '<redacted>' : (value ?? null);
|
|
132
|
+
}
|
|
133
|
+
function formatValue(key, value) {
|
|
134
|
+
const raw = String(value);
|
|
135
|
+
const text = isSecret(key, raw) ? '<redacted>' : raw;
|
|
136
|
+
return /[\s"]/.test(text) ? JSON.stringify(text) : text;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=out.js.map
|
package/dist/out.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"out.js","sourceRoot":"","sources":["../src/out.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a card into a raster, for the places SVG does not go.
|
|
3
|
+
*
|
|
4
|
+
* SVG renders in a GitHub issue and nowhere else that matters: X will not accept it as an upload,
|
|
5
|
+
* and Slack and Discord give it no inline preview. So the card someone actually posts is a PNG,
|
|
6
|
+
* and the one that animates is a GIF.
|
|
7
|
+
*
|
|
8
|
+
* Neither needs a dependency in `package.json`, and deliberately does not have one.
|
|
9
|
+
* `docs/media/README.md` keeps `playwright-core`, `pngjs` and `gifenc` out so that everyone running
|
|
10
|
+
* `npm ci` to work on orca does not pay for a browser download, and a picture command is not a
|
|
11
|
+
* reason to reverse that. They are imported at the moment of use and their absence is an
|
|
12
|
+
* actionable message naming the one line that fixes it — the same line the docs already give for
|
|
13
|
+
* regenerating README art.
|
|
14
|
+
*
|
|
15
|
+
* `scripts/render-demo.mjs` established the encoding approach and the reason for it: the ffmpeg
|
|
16
|
+
* that ships with Playwright is stripped to what Playwright needs and has neither a PNG decoder
|
|
17
|
+
* nor a GIF muxer, so shelling out to it fails silently at both ends. The encoder is JavaScript
|
|
18
|
+
* for that reason.
|
|
19
|
+
*/
|
|
20
|
+
/** Everything the raster path needs, in the order the install line should list them. */
|
|
21
|
+
export declare const RENDER_DEPS: readonly ["playwright-core", "pngjs", "gifenc"];
|
|
22
|
+
export type CardFormat = 'svg' | 'png' | 'gif';
|
|
23
|
+
export interface CardTarget {
|
|
24
|
+
path: string;
|
|
25
|
+
format: CardFormat;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The path and format to write a card to, or a refusal naming what orca can write.
|
|
29
|
+
*
|
|
30
|
+
* Refusing beats succeeding falsely. Writing SVG bytes into a file called `.png` produced a file
|
|
31
|
+
* no image viewer opens and a success line explaining nothing, and `.png` is precisely the request
|
|
32
|
+
* people make, because it is the format that posts where SVG does not.
|
|
33
|
+
*/
|
|
34
|
+
export declare function cardTarget(name: string, flag: string): CardTarget;
|
|
35
|
+
export interface GifFrame {
|
|
36
|
+
/** How many events of the chain this frame shows. */
|
|
37
|
+
reveal: number;
|
|
38
|
+
/** How long it is held, in milliseconds. */
|
|
39
|
+
delayMs: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* One frame per hop, with the hold carried in the frame rather than in a frame rate.
|
|
43
|
+
*
|
|
44
|
+
* The alternative — a frame every 40ms — is how 107 near-identical PNGs became a 5.5 MB GIF for
|
|
45
|
+
* the README hero, when 22 self-timed frames said exactly the same thing. The last frame rests
|
|
46
|
+
* long enough to read the finished card before the loop starts over.
|
|
47
|
+
*/
|
|
48
|
+
export declare function gifFrames(events: number): GifFrame[];
|
|
49
|
+
/** Which of the optional packages are not installed. Empty means the raster path will work. */
|
|
50
|
+
export declare function missingRenderDeps(): Promise<string[]>;
|
|
51
|
+
/** The message shown when a raster was asked for and the toolchain is not here. */
|
|
52
|
+
export declare function installHint(missing: string[], format: CardFormat): string;
|
|
53
|
+
/** One SVG as a PNG, at 2× so it stays sharp when a client scales it down. */
|
|
54
|
+
export declare function svgToPng(svg: string): Promise<Buffer>;
|
|
55
|
+
/**
|
|
56
|
+
* A sequence of SVGs as one animated GIF.
|
|
57
|
+
*
|
|
58
|
+
* The palette is quantised once from the final frame, which carries every colour the sequence
|
|
59
|
+
* ever shows, so nothing shifts hue partway through — the failure mode of quantising per frame.
|
|
60
|
+
*/
|
|
61
|
+
export declare function svgsToGif(svgs: string[], delays: number[]): Promise<Buffer>;
|
|
62
|
+
//# sourceMappingURL=rasterize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rasterize.d.ts","sourceRoot":"","sources":["../src/rasterize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAaH,wFAAwF;AACxF,eAAO,MAAM,WAAW,iDAAkD,CAAC;AAE3E,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAE/C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAQjE;AAED,MAAM,WAAW,QAAQ;IACvB,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;CACjB;AAKD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,EAAE,CAMpD;AAED,+FAA+F;AAC/F,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAU3D;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAOzE;AAwCD,8EAA8E;AAC9E,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ3D;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA0CjF"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a card into a raster, for the places SVG does not go.
|
|
3
|
+
*
|
|
4
|
+
* SVG renders in a GitHub issue and nowhere else that matters: X will not accept it as an upload,
|
|
5
|
+
* and Slack and Discord give it no inline preview. So the card someone actually posts is a PNG,
|
|
6
|
+
* and the one that animates is a GIF.
|
|
7
|
+
*
|
|
8
|
+
* Neither needs a dependency in `package.json`, and deliberately does not have one.
|
|
9
|
+
* `docs/media/README.md` keeps `playwright-core`, `pngjs` and `gifenc` out so that everyone running
|
|
10
|
+
* `npm ci` to work on orca does not pay for a browser download, and a picture command is not a
|
|
11
|
+
* reason to reverse that. They are imported at the moment of use and their absence is an
|
|
12
|
+
* actionable message naming the one line that fixes it — the same line the docs already give for
|
|
13
|
+
* regenerating README art.
|
|
14
|
+
*
|
|
15
|
+
* `scripts/render-demo.mjs` established the encoding approach and the reason for it: the ffmpeg
|
|
16
|
+
* that ships with Playwright is stripped to what Playwright needs and has neither a PNG decoder
|
|
17
|
+
* nor a GIF muxer, so shelling out to it fails silently at both ends. The encoder is JavaScript
|
|
18
|
+
* for that reason.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Import a package orca does not depend on.
|
|
22
|
+
*
|
|
23
|
+
* The specifier is a variable on purpose: a literal makes TypeScript resolve the module at build
|
|
24
|
+
* time, and these are precisely the modules that are not there. Typing the result as `unknown`
|
|
25
|
+
* keeps every call site honest about that.
|
|
26
|
+
*/
|
|
27
|
+
async function loadOptional(name) {
|
|
28
|
+
return (await import(name));
|
|
29
|
+
}
|
|
30
|
+
/** Everything the raster path needs, in the order the install line should list them. */
|
|
31
|
+
export const RENDER_DEPS = ['playwright-core', 'pngjs', 'gifenc'];
|
|
32
|
+
/**
|
|
33
|
+
* The path and format to write a card to, or a refusal naming what orca can write.
|
|
34
|
+
*
|
|
35
|
+
* Refusing beats succeeding falsely. Writing SVG bytes into a file called `.png` produced a file
|
|
36
|
+
* no image viewer opens and a success line explaining nothing, and `.png` is precisely the request
|
|
37
|
+
* people make, because it is the format that posts where SVG does not.
|
|
38
|
+
*/
|
|
39
|
+
export function cardTarget(name, flag) {
|
|
40
|
+
const ext = /\.([A-Za-z0-9]+)$/.exec(name)?.[1]?.toLowerCase();
|
|
41
|
+
if (ext === 'svg' || ext === 'png' || ext === 'gif')
|
|
42
|
+
return { path: name, format: ext };
|
|
43
|
+
throw new Error(ext === undefined
|
|
44
|
+
? `${flag} needs a filename ending in .svg, .png or .gif — got ${JSON.stringify(name)}`
|
|
45
|
+
: `${flag} cannot write .${ext}. Name it .svg, .png or .gif instead.`);
|
|
46
|
+
}
|
|
47
|
+
const FRAME_HOLD_MS = 520;
|
|
48
|
+
const FINAL_HOLD_MS = 2600;
|
|
49
|
+
/**
|
|
50
|
+
* One frame per hop, with the hold carried in the frame rather than in a frame rate.
|
|
51
|
+
*
|
|
52
|
+
* The alternative — a frame every 40ms — is how 107 near-identical PNGs became a 5.5 MB GIF for
|
|
53
|
+
* the README hero, when 22 self-timed frames said exactly the same thing. The last frame rests
|
|
54
|
+
* long enough to read the finished card before the loop starts over.
|
|
55
|
+
*/
|
|
56
|
+
export function gifFrames(events) {
|
|
57
|
+
if (events <= 0)
|
|
58
|
+
return [];
|
|
59
|
+
return Array.from({ length: events }, (_, i) => ({
|
|
60
|
+
reveal: i + 1,
|
|
61
|
+
delayMs: i === events - 1 ? FINAL_HOLD_MS : FRAME_HOLD_MS,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
/** Which of the optional packages are not installed. Empty means the raster path will work. */
|
|
65
|
+
export async function missingRenderDeps() {
|
|
66
|
+
const missing = [];
|
|
67
|
+
for (const name of RENDER_DEPS) {
|
|
68
|
+
try {
|
|
69
|
+
await loadOptional(name);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
missing.push(name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return missing;
|
|
76
|
+
}
|
|
77
|
+
/** The message shown when a raster was asked for and the toolchain is not here. */
|
|
78
|
+
export function installHint(missing, format) {
|
|
79
|
+
return (`writing a .${format} needs ${missing.join(', ')}, which orca does not depend on: ` +
|
|
80
|
+
'a browser download is not something everyone running `npm ci` should pay for.\n' +
|
|
81
|
+
` npm i --no-save ${RENDER_DEPS.join(' ')}\n` +
|
|
82
|
+
' …then run this again, or write a .svg, which never needs anything.');
|
|
83
|
+
}
|
|
84
|
+
async function requireRenderDeps(format) {
|
|
85
|
+
const missing = await missingRenderDeps();
|
|
86
|
+
if (missing.length > 0)
|
|
87
|
+
throw new Error(installHint(missing, format));
|
|
88
|
+
}
|
|
89
|
+
/** Load Playwright's Chromium, preferring an explicit executable when one is configured. */
|
|
90
|
+
async function launchChromium() {
|
|
91
|
+
const { chromium } = (await loadOptional('playwright-core'));
|
|
92
|
+
const exe = process.env['ORCA_CHROMIUM'] ?? process.env['PLAYWRIGHT_CHROMIUM_EXECUTABLE'];
|
|
93
|
+
const browser = await chromium.launch(exe === undefined ? {} : { executablePath: exe });
|
|
94
|
+
return {
|
|
95
|
+
async shot(svg) {
|
|
96
|
+
const page = await browser.newPage({ deviceScaleFactor: 2 });
|
|
97
|
+
// A transparent body would composite the card onto whatever the viewer paints behind it.
|
|
98
|
+
// The card draws its own ground, so the page only has to not add one of its own.
|
|
99
|
+
await page.setContent(`<body style="margin:0">${svg}</body>`);
|
|
100
|
+
const buffer = await page.locator('svg').screenshot({ omitBackground: true });
|
|
101
|
+
await page.close();
|
|
102
|
+
return buffer;
|
|
103
|
+
},
|
|
104
|
+
close: () => browser.close(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** One SVG as a PNG, at 2× so it stays sharp when a client scales it down. */
|
|
108
|
+
export async function svgToPng(svg) {
|
|
109
|
+
await requireRenderDeps('png');
|
|
110
|
+
const browser = await launchChromium();
|
|
111
|
+
try {
|
|
112
|
+
return await browser.shot(svg);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await browser.close();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A sequence of SVGs as one animated GIF.
|
|
120
|
+
*
|
|
121
|
+
* The palette is quantised once from the final frame, which carries every colour the sequence
|
|
122
|
+
* ever shows, so nothing shifts hue partway through — the failure mode of quantising per frame.
|
|
123
|
+
*/
|
|
124
|
+
export async function svgsToGif(svgs, delays) {
|
|
125
|
+
await requireRenderDeps('gif');
|
|
126
|
+
if (svgs.length === 0)
|
|
127
|
+
throw new Error('nothing to animate: the chain has no events');
|
|
128
|
+
const { PNG } = (await loadOptional('pngjs'));
|
|
129
|
+
const gifenc = (await loadOptional('gifenc'));
|
|
130
|
+
const mod = (gifenc.GIFEncoder ? gifenc : (gifenc.default ?? gifenc));
|
|
131
|
+
const browser = await launchChromium();
|
|
132
|
+
try {
|
|
133
|
+
const frames = [];
|
|
134
|
+
for (const svg of svgs)
|
|
135
|
+
frames.push(PNG.sync.read(await browser.shot(svg)));
|
|
136
|
+
const enc = mod.GIFEncoder();
|
|
137
|
+
const last = frames[frames.length - 1];
|
|
138
|
+
const palette = mod.quantize(new Uint8ClampedArray(last.data), 32, { format: 'rgb565' });
|
|
139
|
+
for (const [i, frame] of frames.entries()) {
|
|
140
|
+
const indexed = mod.applyPalette(new Uint8ClampedArray(frame.data), palette, 'rgb565');
|
|
141
|
+
enc.writeFrame(indexed, frame.width, frame.height, {
|
|
142
|
+
palette: i === 0 ? palette : undefined,
|
|
143
|
+
delay: delays[i] ?? FRAME_HOLD_MS,
|
|
144
|
+
repeat: 0,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
enc.finish();
|
|
148
|
+
return Buffer.from(enc.bytes());
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
await browser.close();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=rasterize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rasterize.js","sourceRoot":"","sources":["../src/rasterize.ts"],"names":[],"mappings":"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"}
|
package/dist/serial.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A one-at-a-time work queue for trace writes.
|
|
3
|
+
*
|
|
4
|
+
* The recorder is driven by proxy callbacks, which arrive whenever an exchange completes. Running
|
|
5
|
+
* those handlers concurrently is not merely untidy: each one snapshots the workspace, and two
|
|
6
|
+
* overlapping `git add` invocations collide on `index.lock` and fail. Serializing also keeps
|
|
7
|
+
* `seq` in the order exchanges actually happened, which the whole format depends on.
|
|
8
|
+
*
|
|
9
|
+
* A failing task must not stall or silently empty the queue — one bad write should cost one event,
|
|
10
|
+
* not the rest of the run — so errors are collected and re-thrown at `drain()`.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SerialQueue {
|
|
13
|
+
#private;
|
|
14
|
+
push(task: () => Promise<void>): void;
|
|
15
|
+
/** Wait for everything queued so far, including work queued by those tasks. */
|
|
16
|
+
drain(): Promise<void>;
|
|
17
|
+
errors(): unknown[];
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=serial.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serial.d.ts","sourceRoot":"","sources":["../src/serial.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,qBAAa,WAAW;;IAItB,IAAI,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAOrC,+EAA+E;IACzE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,MAAM,IAAI,OAAO,EAAE;CAGpB"}
|
package/dist/serial.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A one-at-a-time work queue for trace writes.
|
|
3
|
+
*
|
|
4
|
+
* The recorder is driven by proxy callbacks, which arrive whenever an exchange completes. Running
|
|
5
|
+
* those handlers concurrently is not merely untidy: each one snapshots the workspace, and two
|
|
6
|
+
* overlapping `git add` invocations collide on `index.lock` and fail. Serializing also keeps
|
|
7
|
+
* `seq` in the order exchanges actually happened, which the whole format depends on.
|
|
8
|
+
*
|
|
9
|
+
* A failing task must not stall or silently empty the queue — one bad write should cost one event,
|
|
10
|
+
* not the rest of the run — so errors are collected and re-thrown at `drain()`.
|
|
11
|
+
*/
|
|
12
|
+
export class SerialQueue {
|
|
13
|
+
#tail = Promise.resolve();
|
|
14
|
+
#errors = [];
|
|
15
|
+
push(task) {
|
|
16
|
+
this.#tail = this.#tail.then(() => task().catch((err) => void this.#errors.push(err)), () => task().catch((err) => void this.#errors.push(err)));
|
|
17
|
+
}
|
|
18
|
+
/** Wait for everything queued so far, including work queued by those tasks. */
|
|
19
|
+
async drain() {
|
|
20
|
+
let previous;
|
|
21
|
+
// A task may enqueue more work, so keep waiting until the tail stops moving.
|
|
22
|
+
while (previous !== this.#tail) {
|
|
23
|
+
previous = this.#tail;
|
|
24
|
+
await previous;
|
|
25
|
+
}
|
|
26
|
+
const [first] = this.#errors;
|
|
27
|
+
if (first !== undefined)
|
|
28
|
+
throw first instanceof Error ? first : new Error(String(first));
|
|
29
|
+
}
|
|
30
|
+
errors() {
|
|
31
|
+
return this.#errors.slice();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=serial.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serial.js","sourceRoot":"","sources":["../src/serial.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,WAAW;IACtB,KAAK,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAChC,OAAO,GAAc,EAAE,CAAC;IAEjC,IAAI,CAAC,IAAyB;QAC5B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAC1B,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EACjE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAClE,CAAC;IACJ,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,KAAK;QACT,IAAI,QAAmC,CAAC;QACxC,6EAA6E;QAC7E,OAAO,QAAQ,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/B,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;YACtB,MAAM,QAAQ,CAAC;QACjB,CAAC;QACD,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;CACF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { RunGraph } from '@orcareplay/core';
|
|
2
|
+
import type { CompareRow } from './commands/compare.js';
|
|
3
|
+
/**
|
|
4
|
+
* A comparison table as one self-contained SVG.
|
|
5
|
+
*
|
|
6
|
+
* The compare table is the most shareable thing OrcaReplay produces — a model-versus-model result
|
|
7
|
+
* grounded in a real task rather than a synthetic benchmark. It is only worth sharing if it says
|
|
8
|
+
* what it measured, so the card carries the fork point and the verify command alongside the
|
|
9
|
+
* verdicts. A cost column with no stated verdict command is a number people will quote out of
|
|
10
|
+
* context.
|
|
11
|
+
*
|
|
12
|
+
* Monochrome and dependency-free for the same reason the viewer is: it has to survive being
|
|
13
|
+
* dropped into an issue, a README, a chat window, or a dark-mode client, and it has to render
|
|
14
|
+
* with nothing installed.
|
|
15
|
+
*/
|
|
16
|
+
export interface CardMeta {
|
|
17
|
+
runId: string;
|
|
18
|
+
forkPoint: number;
|
|
19
|
+
verify?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function renderCompareCard(rows: CompareRow[], meta: CardMeta): string;
|
|
22
|
+
export interface ChainCardMeta {
|
|
23
|
+
runId: string;
|
|
24
|
+
/**
|
|
25
|
+
* How many of the chain's events to draw, for one frame of an animation. The card keeps the
|
|
26
|
+
* geometry of the whole chain either way — a frame that resized to its own content would make
|
|
27
|
+
* the finished GIF jump as it built.
|
|
28
|
+
*/
|
|
29
|
+
reveal?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* One causal chain as a self-contained SVG.
|
|
33
|
+
*
|
|
34
|
+
* The timeline answers "what happened"; this answers "what produced this", which is the shape of
|
|
35
|
+
* the thing worth showing someone else. It carries three things a picture that travels without
|
|
36
|
+
* its trace has to carry on its own face: the claim, stated first; the command that reproduces it;
|
|
37
|
+
* and — where any hop was inferred — the fact that it was, because a dashed line nobody explains
|
|
38
|
+
* launders a guess into a fact.
|
|
39
|
+
*
|
|
40
|
+
* Monochrome and dependency-free for the same reason the viewer is: it has to survive being
|
|
41
|
+
* dropped into an issue, a README, a chat window or a dark-mode client with nothing installed.
|
|
42
|
+
*/
|
|
43
|
+
export declare function renderChainCard(graph: RunGraph, meta: ChainCardMeta): string;
|
|
44
|
+
export interface GraphCardMeta {
|
|
45
|
+
runId: string;
|
|
46
|
+
/** Seqs to light up — normally one causal chain. Everything else is drawn dim. */
|
|
47
|
+
highlight: Set<number>;
|
|
48
|
+
}
|
|
49
|
+
export interface ScopedGraph extends RunGraph {
|
|
50
|
+
/** True when turns were dropped to keep the picture readable. Said on the card. */
|
|
51
|
+
narrowed: boolean;
|
|
52
|
+
/** Events that belong to no lane, or fell outside the scope. Counted on the card. */
|
|
53
|
+
omitted: number;
|
|
54
|
+
turns: number[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The part of a graph worth drawing, narrowed when a whole run would be a hairball.
|
|
58
|
+
*
|
|
59
|
+
* A pane that draws all 400 events of a real run is strictly worse than the list beside it, so
|
|
60
|
+
* past `max` drawable events this keeps only the turns the highlighted chain touches. Narrowing
|
|
61
|
+
* is recorded rather than silent: a picture of part of a run that looks like a picture of the run
|
|
62
|
+
* is the same failure as an inferred edge that looks recorded.
|
|
63
|
+
*/
|
|
64
|
+
export declare function scopeForCard(graph: RunGraph, highlight: Set<number>, max?: number): ScopedGraph;
|
|
65
|
+
/**
|
|
66
|
+
* A whole run as a causal graph: time left to right, kind of thing top to bottom.
|
|
67
|
+
*
|
|
68
|
+
* The chain card follows one path; this shows the shape. Two things only the shape gives you —
|
|
69
|
+
* the run's repeating motif, so anything that breaks it is worth a look, and an event with no edge
|
|
70
|
+
* leaving it, which is an absence a list cannot show at all.
|
|
71
|
+
*/
|
|
72
|
+
export declare function renderGraphCard(graph: RunGraph | ScopedGraph, meta: GraphCardMeta): string;
|
|
73
|
+
//# sourceMappingURL=share-card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share-card.d.ts","sourceRoot":"","sources":["../src/share-card.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAuD5E;AAMD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAyE5E;AA0GD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC3C,mFAAmF;IACnF,QAAQ,EAAE,OAAO,CAAC;IAClB,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAqBD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,SAAK,GAAG,WAAW,CAwB3F;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,GAAG,WAAW,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAqH1F"}
|