@sverka/sarif-viewer-tui 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +46 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/index.d.mts +94 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +4 -0
- package/dist/input-BZDp8iY2.mjs +20 -0
- package/dist/input-BZDp8iY2.mjs.map +1 -0
- package/dist/render-BR4Ucxcf.mjs +2 -0
- package/dist/render-D5JdWdKp.mjs +291 -0
- package/dist/render-D5JdWdKp.mjs.map +1 -0
- package/package.json +46 -10
package/dist/bin.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/bin.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as resolveFindings } from "./input-BZDp8iY2.mjs";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
//#region src/bin.ts
|
|
6
|
+
async function main() {
|
|
7
|
+
const arg = process.argv[2];
|
|
8
|
+
if (arg === "--help" || arg === "-h") {
|
|
9
|
+
process.stdout.write("Usage: sarif-viewer-tui [file]\n Reads SARIF from a file argument or stdin.\n");
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
let options;
|
|
13
|
+
if (arg !== void 0) options = { sarifPath: arg };
|
|
14
|
+
else if (process.stdin.isTTY) options = null;
|
|
15
|
+
else {
|
|
16
|
+
const raw = readFileSync(0, "utf8");
|
|
17
|
+
if (raw.trim() === "") {
|
|
18
|
+
process.stderr.write("sarif-viewer-tui: stdin is empty.\n");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
options = { sarif: JSON.parse(raw) };
|
|
23
|
+
} catch {
|
|
24
|
+
process.stderr.write("sarif-viewer-tui: failed to parse SARIF JSON from stdin.\n");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (options === null) {
|
|
29
|
+
process.stderr.write("sarif-viewer-tui: no input. Provide a SARIF file argument or pipe SARIF via stdin.\n");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const findings = resolveFindings(options);
|
|
33
|
+
const { renderSarifTui } = await import("./render-BR4Ucxcf.mjs");
|
|
34
|
+
await renderSarifTui({ findings });
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
await main();
|
|
38
|
+
process.exit(0);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
process.stderr.write(`sarif-viewer-tui: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export {};
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=bin.mjs.map
|
package/dist/bin.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.mjs","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n// @sverka/sarif-viewer-tui — CLI entry point. Spec 46.\n\nimport process from \"node:process\";\nimport { readFileSync } from \"node:fs\";\nimport { resolveFindings } from \"./input.js\";\n\nasync function main(): Promise<void> {\n const arg = process.argv[2];\n\n if (arg === \"--help\" || arg === \"-h\") {\n process.stdout.write(\n \"Usage: sarif-viewer-tui [file]\\n Reads SARIF from a file argument or stdin.\\n\",\n );\n return;\n }\n\n // Resolve findings first (handles file reading, parsing, normalization).\n // This does NOT import ink — only the render step does.\n let options: { sarifPath: string } | { sarif: unknown } | null;\n if (arg !== undefined) {\n options = { sarifPath: arg };\n } else if (process.stdin.isTTY) {\n options = null;\n } else {\n const raw = readFileSync(0, \"utf8\");\n if (raw.trim() === \"\") {\n process.stderr.write(\"sarif-viewer-tui: stdin is empty.\\n\");\n process.exit(1);\n }\n try {\n options = { sarif: JSON.parse(raw) };\n } catch {\n process.stderr.write(\"sarif-viewer-tui: failed to parse SARIF JSON from stdin.\\n\");\n process.exit(1);\n }\n }\n\n if (options === null) {\n process.stderr.write(\n \"sarif-viewer-tui: no input. Provide a SARIF file argument or pipe SARIF via stdin.\\n\",\n );\n process.exit(1);\n }\n\n const findings = resolveFindings(options);\n\n // Lazy import the render module — this pulls in ink/react, which we only\n // need when actually launching the TUI.\n const { renderSarifTui } = await import(\"./render.js\");\n await renderSarifTui({ findings });\n}\n\ntry {\n await main();\n process.exit(0);\n} catch (e: unknown) {\n process.stderr.write(\n `sarif-viewer-tui: ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n process.exit(1);\n}\n"],"mappings":";;;;;AAOA,eAAe,OAAsB;CACnC,MAAM,MAAM,QAAQ,KAAK;CAEzB,IAAI,QAAQ,YAAY,QAAQ,MAAM;EACpC,QAAQ,OAAO,MACb,gFACF;EACA;CACF;CAIA,IAAI;CACJ,IAAI,QAAQ,KAAA,GACV,UAAU,EAAE,WAAW,IAAI;MACtB,IAAI,QAAQ,MAAM,OACvB,UAAU;MACL;EACL,MAAM,MAAM,aAAa,GAAG,MAAM;EAClC,IAAI,IAAI,KAAK,MAAM,IAAI;GACrB,QAAQ,OAAO,MAAM,qCAAqC;GAC1D,QAAQ,KAAK,CAAC;EAChB;EACA,IAAI;GACF,UAAU,EAAE,OAAO,KAAK,MAAM,GAAG,EAAE;EACrC,QAAQ;GACN,QAAQ,OAAO,MAAM,4DAA4D;GACjF,QAAQ,KAAK,CAAC;EAChB;CACF;CAEA,IAAI,YAAY,MAAM;EACpB,QAAQ,OAAO,MACb,sFACF;EACA,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,WAAW,gBAAgB,OAAO;CAIxC,MAAM,EAAE,mBAAmB,MAAM,OAAO;CACxC,MAAM,eAAe,EAAE,SAAS,CAAC;AACnC;AAEA,IAAI;CACF,MAAM,KAAK;CACX,QAAQ,KAAK,CAAC;AAChB,SAAS,GAAY;CACnB,QAAQ,OAAO,MACb,qBAAqB,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,EAAE,GAClE;CACA,QAAQ,KAAK,CAAC;AAChB"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Key } from "ink";
|
|
2
|
+
import { DEFAULT_NORMALIZE_CONTEXT, Finding, NormalizeContext, Severity } from "@sverka/verification";
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
/** Options for the standalone SARIF TUI. Exactly one of `sarif`, `sarifPath`,
|
|
5
|
+
* `findings` must be provided. */
|
|
6
|
+
interface SarifTuiOptions {
|
|
7
|
+
/** Pre-parsed SARIF object (validated by normalizeSarif). */
|
|
8
|
+
readonly sarif?: unknown;
|
|
9
|
+
/** Path to a `.sarif` file on disk. Read and normalized. */
|
|
10
|
+
readonly sarifPath?: string;
|
|
11
|
+
/** Pre-normalized findings — used directly, no normalization. */
|
|
12
|
+
readonly findings?: readonly Finding[];
|
|
13
|
+
/** Context for `normalizeSarif`. Defaults to
|
|
14
|
+
* `{ root: cwd, checkIdPrefix: "", defaultConfidence: 0.5 }`. */
|
|
15
|
+
readonly context?: NormalizeContext;
|
|
16
|
+
}
|
|
17
|
+
/** Severity filter cycled by the `f` key. */
|
|
18
|
+
type ViewerFilter = "all" | "critical" | "high" | "medium" | "low" | "info";
|
|
19
|
+
/** Sort mode cycled by the `s` key. */
|
|
20
|
+
type SortMode = "none" | "severity" | "file" | "rule";
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/viewer.d.ts
|
|
23
|
+
/** Mutable store backing the SarifTuiApp. */
|
|
24
|
+
export declare class TuiStore {
|
|
25
|
+
readonly findings: readonly Finding[];
|
|
26
|
+
filter: ViewerFilter;
|
|
27
|
+
search: string;
|
|
28
|
+
searching: boolean;
|
|
29
|
+
sort: SortMode;
|
|
30
|
+
selected: number;
|
|
31
|
+
details: boolean;
|
|
32
|
+
private version;
|
|
33
|
+
private readonly listeners;
|
|
34
|
+
private onQuit;
|
|
35
|
+
constructor(findings: readonly Finding[]);
|
|
36
|
+
subscribe: (listener: () => void) => (() => void);
|
|
37
|
+
getVersion: () => number;
|
|
38
|
+
setQuitHandler(fn: () => void): void;
|
|
39
|
+
notify(): void;
|
|
40
|
+
quit(): void;
|
|
41
|
+
visibleFindings(): readonly Finding[];
|
|
42
|
+
handleInput(input: string, key: Key): void;
|
|
43
|
+
}
|
|
44
|
+
/** The standalone SARIF viewer Ink component. */
|
|
45
|
+
export declare function SarifTuiApp(props: Readonly<{
|
|
46
|
+
findings: readonly Finding[];
|
|
47
|
+
}>): import("react").JSX.Element;
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/render.d.ts
|
|
50
|
+
/**
|
|
51
|
+
* Resolve `options` into findings and render the SARIF TUI. Blocks until the
|
|
52
|
+
* user quits (q / Ctrl-C). Resolves on exit.
|
|
53
|
+
*
|
|
54
|
+
* When stdout is not a TTY (piped output, CI, agents) or no interactive
|
|
55
|
+
* input stream is available, Ink cannot run — the findings are printed as
|
|
56
|
+
* plain text instead so the command still produces useful output and exits.
|
|
57
|
+
*
|
|
58
|
+
* Wraps resolveFindings in a try-catch so invalid input produces a clean
|
|
59
|
+
* error message instead of an unhandled synchronous throw before the
|
|
60
|
+
* promise exists.
|
|
61
|
+
*/
|
|
62
|
+
export declare function renderSarifTui(options: SarifTuiOptions): Promise<void>;
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/filter-model.d.ts
|
|
65
|
+
/** Ordered list of severity filters cycled by the `f` key. */
|
|
66
|
+
export declare const SEVERITY_FILTERS: readonly ViewerFilter[];
|
|
67
|
+
/** Severity rank for sorting: critical > high > medium > low > info. */
|
|
68
|
+
export declare function severityRank(severity: Severity): number;
|
|
69
|
+
/** Filter findings by severity level. `all` returns everything. Pure.
|
|
70
|
+
* Delegates to the shared `@sverka/verification` implementation. */
|
|
71
|
+
export declare function filterBySeverity(findings: readonly Finding[], filter: ViewerFilter): readonly Finding[];
|
|
72
|
+
/** Substring match (case-insensitive) across message, checkId, file, rule. Pure.
|
|
73
|
+
* Delegates to the shared `@sverka/verification` implementation. */
|
|
74
|
+
export declare function searchFindings(findings: readonly Finding[], query: string): readonly Finding[];
|
|
75
|
+
/** Sort findings by mode. Sort is stable (preserves original order for equal
|
|
76
|
+
* keys). `none` returns the input unchanged. Pure. */
|
|
77
|
+
export declare function sortFindings(findings: readonly Finding[], mode: SortMode): readonly Finding[];
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/input.d.ts
|
|
80
|
+
/**
|
|
81
|
+
* Resolve `SarifTuiOptions` into a `Finding[]`. Exactly one of `sarif`,
|
|
82
|
+
* `sarifPath`, `findings` must be provided.
|
|
83
|
+
*
|
|
84
|
+
* Delegates to `@sverka/verification`'s `resolveSarifInput` for the shared
|
|
85
|
+
* resolution logic (file reading, JSON parsing, normalization).
|
|
86
|
+
*
|
|
87
|
+
* @throws {Error} when zero or more than one input is provided.
|
|
88
|
+
* @throws {NormalizationError} when SARIF is invalid (propagated).
|
|
89
|
+
* @throws {Error} when the file cannot be read or parsed.
|
|
90
|
+
*/
|
|
91
|
+
export declare function resolveFindings(options: SarifTuiOptions): Finding[];
|
|
92
|
+
//#endregion
|
|
93
|
+
export { DEFAULT_NORMALIZE_CONTEXT, type SarifTuiOptions, type SortMode, type ViewerFilter };
|
|
94
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/viewer.tsx","../src/render.ts","../src/filter-model.ts","../src/input.ts"],"mappings":";;;;;UAMiB;;WAEN;;WAEA;;WAEA,oBAAoB;;;WAGpB,UAAU;;;KAIT;;KASA;;;;qBCbC;WACF,mBAAmB;EAC5B,QAAQ;EACR;EACA;EACA,MAAM;EACN;EACA;UAEQ;mBACS;UACT;EAEI,YAAA,mBAAmB;EAI/B,YAAa;EAOb;EAEA,eAAe;EAIf;EAKA;EAIA,4BAA4B;EAM5B,YAAY,eAAe,KAAK;;;wBAiFlB,YAAY,OAAO;EAAW,mBAAmB;qBAAY,IAAA;;;;;;;;;;;;;;;wBCrH7D,eAAe,SAAS,kBAAkB;;;;qBCjB7C,2BAA2B;;wBAUxB,aAAa,UAAU;;;wBAiBvB,iBACd,mBAAmB,WACnB,QAAQ,wBACE;;;wBAMI,eACd,mBAAmB,WACnB,yBACU;;;wBAMI,aACd,mBAAmB,WACnB,MAAM,oBACI;;;;;;;;;;;;;;wBCvCI,gBAAgB,SAAS,kBAAkB"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { a as filterBySeverity, c as sortFindings, i as SEVERITY_FILTERS, n as SarifTuiApp, o as searchFindings, r as TuiStore, s as severityRank, t as renderSarifTui } from "./render-D5JdWdKp.mjs";
|
|
2
|
+
import { t as resolveFindings } from "./input-BZDp8iY2.mjs";
|
|
3
|
+
import { DEFAULT_NORMALIZE_CONTEXT } from "@sverka/verification";
|
|
4
|
+
export { DEFAULT_NORMALIZE_CONTEXT, SEVERITY_FILTERS, SarifTuiApp, TuiStore, filterBySeverity, renderSarifTui, resolveFindings, searchFindings, severityRank, sortFindings };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { resolveSarifInput } from "@sverka/verification";
|
|
2
|
+
//#region src/input.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolve `SarifTuiOptions` into a `Finding[]`. Exactly one of `sarif`,
|
|
5
|
+
* `sarifPath`, `findings` must be provided.
|
|
6
|
+
*
|
|
7
|
+
* Delegates to `@sverka/verification`'s `resolveSarifInput` for the shared
|
|
8
|
+
* resolution logic (file reading, JSON parsing, normalization).
|
|
9
|
+
*
|
|
10
|
+
* @throws {Error} when zero or more than one input is provided.
|
|
11
|
+
* @throws {NormalizationError} when SARIF is invalid (propagated).
|
|
12
|
+
* @throws {Error} when the file cannot be read or parsed.
|
|
13
|
+
*/
|
|
14
|
+
function resolveFindings(options) {
|
|
15
|
+
return resolveSarifInput(options);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { resolveFindings as t };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=input-BZDp8iY2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-BZDp8iY2.mjs","names":[],"sources":["../src/input.ts"],"sourcesContent":["// @sverka/sarif-viewer-tui — input resolution. Spec 46.\n\nimport { resolveSarifInput, type Finding } from \"@sverka/verification\";\nimport type { SarifTuiOptions } from \"./types.js\";\n\n/**\n * Resolve `SarifTuiOptions` into a `Finding[]`. Exactly one of `sarif`,\n * `sarifPath`, `findings` must be provided.\n *\n * Delegates to `@sverka/verification`'s `resolveSarifInput` for the shared\n * resolution logic (file reading, JSON parsing, normalization).\n *\n * @throws {Error} when zero or more than one input is provided.\n * @throws {NormalizationError} when SARIF is invalid (propagated).\n * @throws {Error} when the file cannot be read or parsed.\n */\nexport function resolveFindings(options: SarifTuiOptions): Finding[] {\n return resolveSarifInput(options);\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,SAAgB,gBAAgB,SAAqC;CACnE,OAAO,kBAAkB,OAAO;AAClC"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { t as resolveFindings } from "./input-BZDp8iY2.mjs";
|
|
2
|
+
import { createElement, useEffect, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
import { Box, Text, render, useApp, useInput, useStdout } from "ink";
|
|
4
|
+
import { filterBySeverity, handleSearchInput, isQuitInput, searchFindings } from "@sverka/verification";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { openSync } from "node:fs";
|
|
8
|
+
import { ReadStream } from "node:tty";
|
|
9
|
+
//#region src/filter-model.ts
|
|
10
|
+
/** Ordered list of severity filters cycled by the `f` key. */
|
|
11
|
+
const SEVERITY_FILTERS = [
|
|
12
|
+
"all",
|
|
13
|
+
"critical",
|
|
14
|
+
"high",
|
|
15
|
+
"medium",
|
|
16
|
+
"low",
|
|
17
|
+
"info"
|
|
18
|
+
];
|
|
19
|
+
/** Severity rank for sorting: critical > high > medium > low > info. */
|
|
20
|
+
function severityRank(severity) {
|
|
21
|
+
switch (severity) {
|
|
22
|
+
case "critical": return 4;
|
|
23
|
+
case "high": return 3;
|
|
24
|
+
case "medium": return 2;
|
|
25
|
+
case "low": return 1;
|
|
26
|
+
case "info": return 0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Filter findings by severity level. `all` returns everything. Pure.
|
|
30
|
+
* Delegates to the shared `@sverka/verification` implementation. */
|
|
31
|
+
function filterBySeverity$1(findings, filter) {
|
|
32
|
+
return filterBySeverity(findings, filter);
|
|
33
|
+
}
|
|
34
|
+
/** Substring match (case-insensitive) across message, checkId, file, rule. Pure.
|
|
35
|
+
* Delegates to the shared `@sverka/verification` implementation. */
|
|
36
|
+
function searchFindings$1(findings, query) {
|
|
37
|
+
return searchFindings(findings, query);
|
|
38
|
+
}
|
|
39
|
+
/** Sort findings by mode. Sort is stable (preserves original order for equal
|
|
40
|
+
* keys). `none` returns the input unchanged. Pure. */
|
|
41
|
+
function sortFindings(findings, mode) {
|
|
42
|
+
if (mode === "none") return findings;
|
|
43
|
+
const indexed = findings.map((f, i) => ({
|
|
44
|
+
f,
|
|
45
|
+
i
|
|
46
|
+
}));
|
|
47
|
+
switch (mode) {
|
|
48
|
+
case "severity":
|
|
49
|
+
indexed.sort((a, b) => severityRank(b.f.severity) - severityRank(a.f.severity) || a.i - b.i);
|
|
50
|
+
break;
|
|
51
|
+
case "file":
|
|
52
|
+
indexed.sort((a, b) => a.f.file.localeCompare(b.f.file, "en") || a.f.startLine - b.f.startLine || a.i - b.i);
|
|
53
|
+
break;
|
|
54
|
+
case "rule": indexed.sort((a, b) => a.f.rule.localeCompare(b.f.rule, "en") || a.i - b.i);
|
|
55
|
+
}
|
|
56
|
+
return indexed.map(({ f }) => f);
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/viewer.tsx
|
|
60
|
+
/** Mutable store backing the SarifTuiApp. */
|
|
61
|
+
var TuiStore = class {
|
|
62
|
+
findings;
|
|
63
|
+
filter = "all";
|
|
64
|
+
search = "";
|
|
65
|
+
searching = false;
|
|
66
|
+
sort = "none";
|
|
67
|
+
selected = 0;
|
|
68
|
+
details = false;
|
|
69
|
+
version = 0;
|
|
70
|
+
listeners = /* @__PURE__ */ new Set();
|
|
71
|
+
onQuit = null;
|
|
72
|
+
constructor(findings) {
|
|
73
|
+
this.findings = findings;
|
|
74
|
+
}
|
|
75
|
+
subscribe = (listener) => {
|
|
76
|
+
this.listeners.add(listener);
|
|
77
|
+
return () => {
|
|
78
|
+
this.listeners.delete(listener);
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
getVersion = () => this.version;
|
|
82
|
+
setQuitHandler(fn) {
|
|
83
|
+
this.onQuit = fn;
|
|
84
|
+
}
|
|
85
|
+
notify() {
|
|
86
|
+
this.version += 1;
|
|
87
|
+
for (const l of this.listeners) l();
|
|
88
|
+
}
|
|
89
|
+
quit() {
|
|
90
|
+
this.onQuit?.();
|
|
91
|
+
}
|
|
92
|
+
visibleFindings() {
|
|
93
|
+
return sortFindings(searchFindings$1(filterBySeverity$1(this.findings, this.filter), this.search), this.sort);
|
|
94
|
+
}
|
|
95
|
+
handleInput(input, key) {
|
|
96
|
+
if (handleSearchInput(input, key, this)) {
|
|
97
|
+
this.notify();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (isQuitInput(input, key)) {
|
|
101
|
+
this.quit();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (input === "f") {
|
|
105
|
+
const i = SEVERITY_FILTERS.indexOf(this.filter);
|
|
106
|
+
this.filter = SEVERITY_FILTERS[(i + 1) % SEVERITY_FILTERS.length] ?? "all";
|
|
107
|
+
this.selected = Math.min(this.selected, Math.max(0, this.visibleFindings().length - 1));
|
|
108
|
+
} else if (input === "s") {
|
|
109
|
+
this.sort = nextSortMode(this.sort);
|
|
110
|
+
this.selected = Math.min(this.selected, Math.max(0, this.visibleFindings().length - 1));
|
|
111
|
+
} else if (input === "d") this.details = !this.details;
|
|
112
|
+
else if (input === "j" || key.downArrow) {
|
|
113
|
+
const max = Math.max(0, this.visibleFindings().length - 1);
|
|
114
|
+
this.selected = Math.min(max, this.selected + 1);
|
|
115
|
+
} else if (input === "k" || key.upArrow) this.selected = Math.max(0, this.selected - 1);
|
|
116
|
+
else return;
|
|
117
|
+
this.notify();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const SORT_CYCLE = [
|
|
121
|
+
"none",
|
|
122
|
+
"severity",
|
|
123
|
+
"file",
|
|
124
|
+
"rule"
|
|
125
|
+
];
|
|
126
|
+
function nextSortMode(mode) {
|
|
127
|
+
const i = SORT_CYCLE.indexOf(mode);
|
|
128
|
+
return SORT_CYCLE[(i + 1) % SORT_CYCLE.length] ?? "none";
|
|
129
|
+
}
|
|
130
|
+
/** Map a severity to a color prop object. */
|
|
131
|
+
function severityColor(severity) {
|
|
132
|
+
if (severity === "critical" || severity === "high") return { color: "red" };
|
|
133
|
+
if (severity === "medium") return { color: "yellow" };
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
/** Render the search indicator based on store state. */
|
|
137
|
+
function renderSearchIndicator(store) {
|
|
138
|
+
if (store.searching) return ` /${store.search}▏`;
|
|
139
|
+
if (store.search) return ` /${store.search}`;
|
|
140
|
+
return "";
|
|
141
|
+
}
|
|
142
|
+
/** Detail lines for the selected finding. */
|
|
143
|
+
function detailsForFinding(finding) {
|
|
144
|
+
return [
|
|
145
|
+
` rule: ${finding.rule}`,
|
|
146
|
+
` file: ${finding.file}`,
|
|
147
|
+
` lines: ${finding.startLine}–${finding.endLine}`,
|
|
148
|
+
` message: ${finding.message}`,
|
|
149
|
+
...finding.helpUrl ? [` help: ${finding.helpUrl}`] : []
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
/** Compute the viewport window for the visible findings list. */
|
|
153
|
+
function computeViewport(visible, selected, termRows, detailsCount) {
|
|
154
|
+
const reservedRows = 4 + detailsCount;
|
|
155
|
+
const maxVisibleRows = Math.max(1, termRows - reservedRows);
|
|
156
|
+
const start = Math.min(Math.max(0, selected - Math.floor(maxVisibleRows / 2)), Math.max(0, visible.length - maxVisibleRows));
|
|
157
|
+
const end = Math.min(visible.length, start + maxVisibleRows);
|
|
158
|
+
return {
|
|
159
|
+
start,
|
|
160
|
+
end,
|
|
161
|
+
items: visible.slice(start, end)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/** The standalone SARIF viewer Ink component. */
|
|
165
|
+
function SarifTuiApp(props) {
|
|
166
|
+
const storeRef = useRef(null);
|
|
167
|
+
if (storeRef.current === null) storeRef.current = new TuiStore(props.findings);
|
|
168
|
+
const store = storeRef.current;
|
|
169
|
+
useSyncExternalStore(store.subscribe, store.getVersion);
|
|
170
|
+
const { exit } = useApp();
|
|
171
|
+
const { stdout } = useStdout();
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
store.setQuitHandler(exit);
|
|
174
|
+
}, [store, exit]);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (store.findings !== props.findings) {
|
|
177
|
+
store.findings = props.findings;
|
|
178
|
+
store.selected = Math.min(store.selected, Math.max(0, store.visibleFindings().length - 1));
|
|
179
|
+
store.notify();
|
|
180
|
+
}
|
|
181
|
+
}, [props.findings, store]);
|
|
182
|
+
useInput((input, key) => {
|
|
183
|
+
store.handleInput(input, key);
|
|
184
|
+
}, { isActive: true });
|
|
185
|
+
const termCols = stdout.columns || 80;
|
|
186
|
+
const termRows = stdout.rows || 24;
|
|
187
|
+
const maxWidth = Math.max(10, termCols - 2);
|
|
188
|
+
const visible = store.visibleFindings();
|
|
189
|
+
const selected = Math.min(store.selected, Math.max(0, visible.length - 1));
|
|
190
|
+
const selectedFinding = visible[selected];
|
|
191
|
+
const detailsLines = store.details && selectedFinding ? detailsForFinding(selectedFinding) : [];
|
|
192
|
+
const { start: viewportStart, items: viewportItems } = computeViewport(visible, selected, termRows, detailsLines.length);
|
|
193
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
194
|
+
flexDirection: "column",
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsx(Text, {
|
|
197
|
+
bold: true,
|
|
198
|
+
children: "SARIF viewer"
|
|
199
|
+
}),
|
|
200
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
201
|
+
"Findings ",
|
|
202
|
+
SEVERITY_FILTERS.map((f) => /* @__PURE__ */ jsxs(Text, {
|
|
203
|
+
inverse: store.filter === f,
|
|
204
|
+
bold: store.filter === f,
|
|
205
|
+
children: [
|
|
206
|
+
"[",
|
|
207
|
+
f,
|
|
208
|
+
"]"
|
|
209
|
+
]
|
|
210
|
+
}, f)),
|
|
211
|
+
` (${visible.length})`,
|
|
212
|
+
renderSearchIndicator(store)
|
|
213
|
+
] }),
|
|
214
|
+
visible.length === 0 ? /* @__PURE__ */ jsx(Text, { children: "No findings" }) : viewportItems.map((f, i) => {
|
|
215
|
+
const actualIndex = viewportStart + i;
|
|
216
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
217
|
+
...actualIndex === selected ? {} : severityColor(f.severity),
|
|
218
|
+
inverse: actualIndex === selected,
|
|
219
|
+
wrap: "truncate",
|
|
220
|
+
children: ` ${f.severity.padEnd(8)} ${f.checkId} ${f.file}:${f.startLine} ${f.message}`.slice(0, maxWidth)
|
|
221
|
+
}, f.id);
|
|
222
|
+
}),
|
|
223
|
+
detailsLines.map((l) => /* @__PURE__ */ jsx(Text, {
|
|
224
|
+
color: "gray",
|
|
225
|
+
wrap: "truncate",
|
|
226
|
+
children: l.slice(0, maxWidth)
|
|
227
|
+
}, `d-${l}`)),
|
|
228
|
+
/* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsx(Text, {
|
|
229
|
+
color: "gray",
|
|
230
|
+
children: "q quit · j/k move · / search · f filter · s sort · d details"
|
|
231
|
+
}) })
|
|
232
|
+
]
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region src/render.ts
|
|
237
|
+
/**
|
|
238
|
+
* Resolve `options` into findings and render the SARIF TUI. Blocks until the
|
|
239
|
+
* user quits (q / Ctrl-C). Resolves on exit.
|
|
240
|
+
*
|
|
241
|
+
* When stdout is not a TTY (piped output, CI, agents) or no interactive
|
|
242
|
+
* input stream is available, Ink cannot run — the findings are printed as
|
|
243
|
+
* plain text instead so the command still produces useful output and exits.
|
|
244
|
+
*
|
|
245
|
+
* Wraps resolveFindings in a try-catch so invalid input produces a clean
|
|
246
|
+
* error message instead of an unhandled synchronous throw before the
|
|
247
|
+
* promise exists.
|
|
248
|
+
*/
|
|
249
|
+
function renderSarifTui(options) {
|
|
250
|
+
let findings;
|
|
251
|
+
try {
|
|
252
|
+
findings = resolveFindings(options);
|
|
253
|
+
} catch (e) {
|
|
254
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
255
|
+
process.stderr.write(`sarif-viewer-tui: ${msg}\n`);
|
|
256
|
+
return Promise.resolve();
|
|
257
|
+
}
|
|
258
|
+
if (!process.stdout.isTTY || process.env.CI) {
|
|
259
|
+
printFindings(findings);
|
|
260
|
+
return Promise.resolve();
|
|
261
|
+
}
|
|
262
|
+
const stdin = interactiveStdin();
|
|
263
|
+
if (stdin === void 0) {
|
|
264
|
+
printFindings(findings);
|
|
265
|
+
return Promise.resolve();
|
|
266
|
+
}
|
|
267
|
+
return render(createElement(SarifTuiApp, { findings }), {
|
|
268
|
+
stdout: process.stdout,
|
|
269
|
+
stdin,
|
|
270
|
+
exitOnCtrlC: true
|
|
271
|
+
}).waitUntilExit().then(() => void 0);
|
|
272
|
+
}
|
|
273
|
+
/** Return a raw-mode-capable input stream for Ink, or undefined. */
|
|
274
|
+
function interactiveStdin() {
|
|
275
|
+
if (process.stdin.isTTY) return process.stdin;
|
|
276
|
+
if (process.platform === "win32") return void 0;
|
|
277
|
+
try {
|
|
278
|
+
return new ReadStream(openSync("/dev/tty", "r"));
|
|
279
|
+
} catch {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/** Plain-text fallback listing used when the TUI cannot run. */
|
|
284
|
+
function printFindings(findings) {
|
|
285
|
+
process.stdout.write(`SARIF findings (${findings.length}):\n`);
|
|
286
|
+
for (const f of findings) process.stdout.write(` ${f.severity.padEnd(8)} ${f.checkId} ${f.file}:${f.startLine} ${f.message}\n`);
|
|
287
|
+
}
|
|
288
|
+
//#endregion
|
|
289
|
+
export { filterBySeverity$1 as a, sortFindings as c, SEVERITY_FILTERS as i, SarifTuiApp as n, searchFindings$1 as o, TuiStore as r, severityRank as s, renderSarifTui as t };
|
|
290
|
+
|
|
291
|
+
//# sourceMappingURL=render-D5JdWdKp.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-D5JdWdKp.mjs","names":["filterBySeverity","sharedFilterBySeverity","searchFindings","sharedSearchFindings","filterBySeverity","searchFindings"],"sources":["../src/filter-model.ts","../src/viewer.tsx","../src/render.ts"],"sourcesContent":["// @sverka/sarif-viewer-tui — pure filter/search/sort helpers. Spec 46.\n\nimport { filterBySeverity as sharedFilterBySeverity, searchFindings as sharedSearchFindings } from \"@sverka/verification\";\nimport type { Finding, Severity } from \"@sverka/verification\";\nimport type { SortMode, ViewerFilter } from \"./types.js\";\n\n/** Ordered list of severity filters cycled by the `f` key. */\nexport const SEVERITY_FILTERS: readonly ViewerFilter[] = [\n \"all\",\n \"critical\",\n \"high\",\n \"medium\",\n \"low\",\n \"info\",\n];\n\n/** Severity rank for sorting: critical > high > medium > low > info. */\nexport function severityRank(severity: Severity): number {\n switch (severity) {\n case \"critical\":\n return 4;\n case \"high\":\n return 3;\n case \"medium\":\n return 2;\n case \"low\":\n return 1;\n case \"info\":\n return 0;\n }\n}\n\n/** Filter findings by severity level. `all` returns everything. Pure.\n * Delegates to the shared `@sverka/verification` implementation. */\nexport function filterBySeverity(\n findings: readonly Finding[],\n filter: ViewerFilter,\n): readonly Finding[] {\n return sharedFilterBySeverity(findings, filter);\n}\n\n/** Substring match (case-insensitive) across message, checkId, file, rule. Pure.\n * Delegates to the shared `@sverka/verification` implementation. */\nexport function searchFindings(\n findings: readonly Finding[],\n query: string,\n): readonly Finding[] {\n return sharedSearchFindings(findings, query);\n}\n\n/** Sort findings by mode. Sort is stable (preserves original order for equal\n * keys). `none` returns the input unchanged. Pure. */\nexport function sortFindings(\n findings: readonly Finding[],\n mode: SortMode,\n): readonly Finding[] {\n if (mode === \"none\") return findings;\n const indexed = findings.map((f, i) => ({ f, i }));\n switch (mode) {\n case \"severity\":\n indexed.sort(\n (a, b) => severityRank(b.f.severity) - severityRank(a.f.severity) || a.i - b.i,\n );\n break;\n case \"file\":\n indexed.sort(\n (a, b) =>\n a.f.file.localeCompare(b.f.file, \"en\") ||\n a.f.startLine - b.f.startLine ||\n a.i - b.i,\n );\n break;\n case \"rule\":\n indexed.sort(\n (a, b) => a.f.rule.localeCompare(b.f.rule, \"en\") || a.i - b.i,\n );\n break;\n }\n return indexed.map(({ f }) => f);\n}\n","// @sverka/sarif-viewer-tui — Ink TUI component. Spec 46.\n\nimport { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport { Box, Text, useApp, useInput, useStdout } from \"ink\";\nimport type { Key } from \"ink\";\nimport { handleSearchInput, isQuitInput, type Finding } from \"@sverka/verification\";\nimport type { SortMode, ViewerFilter } from \"./types.js\";\nimport {\n SEVERITY_FILTERS,\n filterBySeverity,\n searchFindings,\n sortFindings,\n} from \"./filter-model.js\";\n\n/** Mutable store backing the SarifTuiApp. */\nexport class TuiStore {\n readonly findings: readonly Finding[];\n filter: ViewerFilter = \"all\";\n search = \"\";\n searching = false;\n sort: SortMode = \"none\";\n selected = 0;\n details = false;\n\n private version = 0;\n private readonly listeners = new Set<() => void>();\n private onQuit: (() => void) | null = null;\n\n constructor(findings: readonly Finding[]) {\n this.findings = findings;\n }\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getVersion = (): number => this.version;\n\n setQuitHandler(fn: () => void): void {\n this.onQuit = fn;\n }\n\n notify(): void {\n this.version += 1;\n for (const l of this.listeners) l();\n }\n\n quit(): void {\n this.onQuit?.();\n }\n\n visibleFindings(): readonly Finding[] {\n const filtered = filterBySeverity(this.findings, this.filter);\n const searched = searchFindings(filtered, this.search);\n return sortFindings(searched, this.sort);\n }\n\n handleInput(input: string, key: Key): void {\n if (handleSearchInput(input, key, this)) {\n this.notify();\n return;\n }\n\n if (isQuitInput(input, key)) {\n this.quit();\n return;\n }\n if (input === \"f\") {\n const i = SEVERITY_FILTERS.indexOf(this.filter);\n this.filter = SEVERITY_FILTERS[(i + 1) % SEVERITY_FILTERS.length] ?? \"all\";\n this.selected = Math.min(this.selected, Math.max(0, this.visibleFindings().length - 1));\n } else if (input === \"s\") {\n this.sort = nextSortMode(this.sort);\n this.selected = Math.min(this.selected, Math.max(0, this.visibleFindings().length - 1));\n } else if (input === \"d\") {\n this.details = !this.details;\n } else if (input === \"j\" || key.downArrow) {\n const max = Math.max(0, this.visibleFindings().length - 1);\n this.selected = Math.min(max, this.selected + 1);\n } else if (input === \"k\" || key.upArrow) {\n this.selected = Math.max(0, this.selected - 1);\n } else {\n return;\n }\n this.notify();\n }\n}\n\nconst SORT_CYCLE: readonly SortMode[] = [\"none\", \"severity\", \"file\", \"rule\"];\n\nfunction nextSortMode(mode: SortMode): SortMode {\n const i = SORT_CYCLE.indexOf(mode);\n return SORT_CYCLE[(i + 1) % SORT_CYCLE.length] ?? \"none\";\n}\n\n/** Map a severity to a color prop object. */\nfunction severityColor(severity: string): { color?: string } {\n if (severity === \"critical\" || severity === \"high\") return { color: \"red\" };\n if (severity === \"medium\") return { color: \"yellow\" };\n return {};\n}\n\n/** Render the search indicator based on store state. */\nfunction renderSearchIndicator(store: TuiStore): string {\n if (store.searching) return ` /${store.search}▏`;\n if (store.search) return ` /${store.search}`;\n return \"\";\n}\n\n/** Detail lines for the selected finding. */\nfunction detailsForFinding(finding: Finding): string[] {\n return [\n ` rule: ${finding.rule}`,\n ` file: ${finding.file}`,\n ` lines: ${finding.startLine}–${finding.endLine}`,\n ` message: ${finding.message}`,\n ...(finding.helpUrl ? [` help: ${finding.helpUrl}`] : []),\n ];\n}\n\n/** Compute the viewport window for the visible findings list. */\nfunction computeViewport(\n visible: readonly Finding[],\n selected: number,\n termRows: number,\n detailsCount: number,\n): { start: number; end: number; items: readonly Finding[] } {\n const reservedRows = 4 + detailsCount;\n const maxVisibleRows = Math.max(1, termRows - reservedRows);\n const start = Math.min(\n Math.max(0, selected - Math.floor(maxVisibleRows / 2)),\n Math.max(0, visible.length - maxVisibleRows),\n );\n const end = Math.min(visible.length, start + maxVisibleRows);\n return { start, end, items: visible.slice(start, end) };\n}\n\n/** The standalone SARIF viewer Ink component. */\nexport function SarifTuiApp(props: Readonly<{ findings: readonly Finding[] }>) {\n const storeRef = useRef<TuiStore | null>(null);\n if (storeRef.current === null) {\n storeRef.current = new TuiStore(props.findings);\n }\n const store = storeRef.current;\n useSyncExternalStore(store.subscribe, store.getVersion);\n const { exit } = useApp();\n const { stdout } = useStdout();\n\n useEffect(() => {\n store.setQuitHandler(exit);\n }, [store, exit]);\n\n // Reconcile store when findings prop changes (e.g. hot-reload or re-render\n // with different data). Updates the findings array and clamps selection.\n useEffect(() => {\n if (store.findings !== props.findings) {\n (store as { findings: readonly Finding[] }).findings = props.findings;\n store.selected = Math.min(store.selected, Math.max(0, store.visibleFindings().length - 1));\n store.notify();\n }\n }, [props.findings, store]);\n\n useInput(\n (input, key) => {\n store.handleInput(input, key);\n },\n { isActive: true },\n );\n\n const termCols = stdout.columns || 80;\n const termRows = stdout.rows || 24;\n const maxWidth = Math.max(10, termCols - 2);\n const visible = store.visibleFindings();\n const selected = Math.min(store.selected, Math.max(0, visible.length - 1));\n const selectedFinding = visible[selected];\n\n const detailsLines =\n store.details && selectedFinding ? detailsForFinding(selectedFinding) : [];\n\n const { start: viewportStart, items: viewportItems } = computeViewport(\n visible, selected, termRows, detailsLines.length,\n );\n\n return (\n <Box flexDirection=\"column\">\n <Text bold>SARIF viewer</Text>\n\n <Text>\n {\"Findings \"}\n {SEVERITY_FILTERS.map((f) => (\n <Text key={f} inverse={store.filter === f} bold={store.filter === f}>\n [{f}]\n </Text>\n ))}\n {` (${visible.length})`}\n {renderSearchIndicator(store)}\n </Text>\n\n {visible.length === 0 ? (\n <Text>No findings</Text>\n ) : (\n viewportItems.map((f, i) => {\n const actualIndex = viewportStart + i;\n return (\n <Text\n key={f.id}\n {...(actualIndex === selected ? {} : severityColor(f.severity))}\n inverse={actualIndex === selected}\n wrap=\"truncate\"\n >\n {` ${f.severity.padEnd(8)} ${f.checkId} ${f.file}:${f.startLine} ${f.message}`.slice(\n 0,\n maxWidth,\n )}\n </Text>\n );\n })\n )}\n\n {detailsLines.map((l) => (\n <Text key={`d-${l}`} color=\"gray\" wrap=\"truncate\">\n {l.slice(0, maxWidth)}\n </Text>\n ))}\n\n <Text>\n <Text color=\"gray\">\n q quit · j/k move · / search · f filter · s sort · d details\n </Text>\n </Text>\n </Box>\n );\n}\n","// @sverka/sarif-viewer-tui — render entry point. Spec 46.\n\nimport process from \"node:process\";\nimport { openSync } from \"node:fs\";\nimport { ReadStream } from \"node:tty\";\nimport { render } from \"ink\";\nimport { createElement } from \"react\";\nimport type { Finding } from \"@sverka/verification\";\nimport { resolveFindings } from \"./input.js\";\nimport { SarifTuiApp } from \"./viewer.js\";\nimport type { SarifTuiOptions } from \"./types.js\";\n\n/**\n * Resolve `options` into findings and render the SARIF TUI. Blocks until the\n * user quits (q / Ctrl-C). Resolves on exit.\n *\n * When stdout is not a TTY (piped output, CI, agents) or no interactive\n * input stream is available, Ink cannot run — the findings are printed as\n * plain text instead so the command still produces useful output and exits.\n *\n * Wraps resolveFindings in a try-catch so invalid input produces a clean\n * error message instead of an unhandled synchronous throw before the\n * promise exists.\n */\nexport function renderSarifTui(options: SarifTuiOptions): Promise<void> {\n let findings;\n try {\n findings = resolveFindings(options);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`sarif-viewer-tui: ${msg}\\n`);\n return Promise.resolve();\n }\n\n if (!process.stdout.isTTY || process.env.CI) {\n // Non-interactive stdout or CI — even with a pseudo-TTY there is no\n // user to quit Ink, so a TUI would block forever. Print the findings.\n printFindings(findings);\n return Promise.resolve();\n }\n\n // Ink needs a raw-mode-capable stdin for keyboard input. When SARIF was\n // piped via stdin (e.g. `cat f.sarif | sverka view`), stdin is a pipe —\n // fall back to the controlling terminal so the TUI still works.\n const stdin = interactiveStdin();\n if (stdin === undefined) {\n printFindings(findings);\n return Promise.resolve();\n }\n\n const instance = render(createElement(SarifTuiApp, { findings }), {\n stdout: process.stdout,\n stdin,\n exitOnCtrlC: true,\n });\n return instance.waitUntilExit().then(() => undefined);\n}\n\n/** Return a raw-mode-capable input stream for Ink, or undefined. */\nfunction interactiveStdin(): ReadStream | undefined {\n if (process.stdin.isTTY) return process.stdin;\n // /dev/tty is POSIX-only; on Windows there is no equivalent Node API to\n // reopen console input, so piped stdin degrades to the text fallback.\n if (process.platform === \"win32\") return undefined;\n try {\n return new ReadStream(openSync(\"/dev/tty\", \"r\"));\n } catch {\n // No controlling terminal (CI, detached process) — non-interactive.\n return undefined;\n }\n}\n\n/** Plain-text fallback listing used when the TUI cannot run. */\nfunction printFindings(findings: readonly Finding[]): void {\n process.stdout.write(`SARIF findings (${findings.length}):\\n`);\n for (const f of findings) {\n process.stdout.write(\n ` ${f.severity.padEnd(8)} ${f.checkId} ${f.file}:${f.startLine} ${f.message}\\n`,\n );\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,18 +1,54 @@
|
|
|
1
1
|
{
|
|
2
|
-
"bugs": "https://github.com/sverka-dev/sverka/issues",
|
|
3
|
-
"description": "Placeholder for @sverka/sarif-viewer-tui published by @nx-devkit/prepare-for-release.",
|
|
4
|
-
"homepage": "https://sverka.dev",
|
|
5
|
-
"license": "MIT",
|
|
6
2
|
"name": "@sverka/sarif-viewer-tui",
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
},
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Sverka SARIF viewer TUI — standalone Ink-based terminal viewer for SARIF files",
|
|
5
|
+
"license": "MIT",
|
|
11
6
|
"repository": {
|
|
12
7
|
"type": "git",
|
|
13
8
|
"url": "https://github.com/sverka-dev/sverka",
|
|
14
9
|
"directory": "packages/sarif-viewer-tui"
|
|
15
10
|
},
|
|
11
|
+
"homepage": "https://sverka.dev",
|
|
12
|
+
"bugs": "https://github.com/sverka-dev/sverka/issues",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
16
|
"type": "module",
|
|
17
|
-
"
|
|
18
|
-
|
|
17
|
+
"main": "./dist/index.mjs",
|
|
18
|
+
"module": "./dist/index.mjs",
|
|
19
|
+
"types": "./dist/index.d.mts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.mts",
|
|
23
|
+
"import": "./dist/index.mjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"sarif-viewer-tui": "./dist/bin.mjs"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsdown",
|
|
34
|
+
"test": "vitest run --passWithNoTests",
|
|
35
|
+
"lint": "eslint src",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"ink": ">=5 <8",
|
|
40
|
+
"react": ">=19.2.0 <20"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@sverka/verification": "^0.1.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/react": "^19",
|
|
47
|
+
"ink": "7.1.1",
|
|
48
|
+
"ink-testing-library": "4.0.0",
|
|
49
|
+
"react": "19.3.0",
|
|
50
|
+
"tsdown": "^0.23.0",
|
|
51
|
+
"typescript": "^5.8.0",
|
|
52
|
+
"vitest": "^5.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|