@sverka/ui 0.0.0 → 0.1.2

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.
@@ -0,0 +1,27 @@
1
+ //#region src/server.d.ts
2
+ interface UiServerOptions {
3
+ /** Directory to scan for SARIF files (default: .sverka/artifacts). */
4
+ readonly artifactsDir: string;
5
+ /** Port to listen on (default: 3000). */
6
+ readonly port?: number;
7
+ /** Host to bind (default: localhost). */
8
+ readonly host?: string;
9
+ }
10
+ interface UiServer {
11
+ readonly port: number;
12
+ readonly url: string;
13
+ close(): void;
14
+ }
15
+ /**
16
+ * Start a local web dashboard server that lists SARIF files and
17
+ * renders individual findings reports. Returns when the server is
18
+ * listening.
19
+ */
20
+ export declare function startUiServer(options: UiServerOptions): Promise<UiServer>;
21
+ //#endregion
22
+ //#region src/dashboard.d.ts
23
+ /** Render the dashboard page listing available SARIF files. */
24
+ export declare function renderDashboard(artifactsDir: string, files: readonly string[]): string;
25
+ //#endregion
26
+ export type { UiServer, UiServerOptions };
27
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/server.ts","../src/dashboard.ts"],"mappings":";UAUiB;;WAEN;;WAEA;;WAEA;;UAGM;WACN;WACA;EACT;;;;;;;wBAiBc,cAAc,SAAS,kBAAkB,QAAQ;;;;wBCpCjD,gBAAgB,sBAAsB"}
package/dist/index.mjs ADDED
@@ -0,0 +1,243 @@
1
+ import { createServer } from "node:http";
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
4
+ import { dirname, join, relative, resolve, sep } from "node:path";
5
+ import { normalizeSarif } from "@sverka/verification";
6
+ import { generateSarifHtml } from "@sverka/sarif-viewer-web";
7
+ //#region src/dashboard.ts
8
+ /** Render the dashboard page listing available SARIF files. */
9
+ function renderDashboard(artifactsDir, files) {
10
+ const fileList = files.length === 0 ? "<p class=\"empty\">No SARIF files found. Run <code>sverka run --format sarif</code> to generate findings.</p>" : `<ul class="file-list">
11
+ ${files.map((f) => `<li><a href="/report/${encodeURIComponent(f)}">${escapeHtml(f)}</a></li>`).join("\n ")}
12
+ </ul>`;
13
+ return `<!DOCTYPE html>
14
+ <html lang="en">
15
+ <head>
16
+ <meta charset="utf-8">
17
+ <meta name="viewport" content="width=device-width, initial-scale=1">
18
+ <title>Sverka Dashboard</title>
19
+ <style>${CSS}</style>
20
+ </head>
21
+ <body>
22
+ <header>
23
+ <h1>Sverka Dashboard</h1>
24
+ <p class="path">Artifacts: <code>${escapeHtml(artifactsDir)}</code></p>
25
+ </header>
26
+ <main>
27
+ <h2>SARIF Reports</h2>
28
+ ${fileList}
29
+ </main>
30
+ <footer>
31
+ <a href="https://sverka.dev">sverka.dev</a>
32
+ </footer>
33
+ </body>
34
+ </html>`;
35
+ }
36
+ function escapeHtml(text) {
37
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
38
+ }
39
+ const CSS = String.raw`
40
+ :root { color-scheme: dark; }
41
+ * { box-sizing: border-box; }
42
+ body {
43
+ margin: 0;
44
+ padding: 0;
45
+ background: #0d1117;
46
+ color: #c9d1d9;
47
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
48
+ line-height: 1.6;
49
+ min-height: 100vh;
50
+ display: flex;
51
+ flex-direction: column;
52
+ }
53
+ header {
54
+ padding: 1.5rem 2rem;
55
+ background: #161b22;
56
+ border-bottom: 1px solid #30363d;
57
+ }
58
+ header h1 { margin: 0 0 0.5rem 0; font-size: 1.5rem; color: #f0f6fc; }
59
+ .path { font-size: 0.875rem; color: #8b949e; margin: 0; }
60
+ path code { color: #58a6ff; }
61
+ main { flex: 1; padding: 2rem; }
62
+ h2 { font-size: 1.25rem; color: #f0f6fc; margin: 0 0 1rem 0; }
63
+ .file-list { list-style: none; padding: 0; }
64
+ .file-list li { margin-bottom: 0.5rem; }
65
+ .file-list a {
66
+ display: block;
67
+ padding: 0.75rem 1rem;
68
+ background: #161b22;
69
+ border: 1px solid #30363d;
70
+ border-radius: 6px;
71
+ color: #58a6ff;
72
+ text-decoration: none;
73
+ font-family: monospace;
74
+ font-size: 0.9rem;
75
+ }
76
+ .file-list a:hover { border-color: #58a6ff; background: #1c2330; }
77
+ .empty { color: #8b949e; padding: 2rem; text-align: center; }
78
+ code { background: #21262d; padding: 0.1rem 0.3rem; border-radius: 3px; }
79
+ footer { padding: 1rem 2rem; border-top: 1px solid #21262d; text-align: center; }
80
+ footer a { color: #58a6ff; text-decoration: none; }
81
+ `;
82
+ //#endregion
83
+ //#region src/server.ts
84
+ /** Security headers for all HTML responses — defense-in-depth against XSS.
85
+ * The CSP blocks inline scripts (script-src 'none') even if escaping fails.
86
+ * generateSarifHtml escapes all user-controlled data via escapeHtml(). */
87
+ const HTML_HEADERS = {
88
+ "content-type": "text/html; charset=utf-8",
89
+ "content-security-policy": "default-src 'self'; script-src 'none'; style-src 'unsafe-inline'",
90
+ "x-content-type-options": "nosniff"
91
+ };
92
+ /**
93
+ * Start a local web dashboard server that lists SARIF files and
94
+ * renders individual findings reports. Returns when the server is
95
+ * listening.
96
+ */
97
+ function startUiServer(options) {
98
+ const port = options.port ?? 3e3;
99
+ const host = options.host ?? "localhost";
100
+ const artifactsDir = resolve(options.artifactsDir);
101
+ const server = createServer((req, res) => {
102
+ handleRequest(req, res, artifactsDir);
103
+ });
104
+ return new Promise((resolve, reject) => {
105
+ const onError = (err) => reject(err);
106
+ server.on("error", onError);
107
+ server.listen(port, host, () => {
108
+ const address = server.address();
109
+ const actualPort = typeof address === "object" && address ? address.port : port;
110
+ server.removeListener("error", onError);
111
+ server.on("error", () => {});
112
+ resolve({
113
+ port: actualPort,
114
+ url: `http://${host}:${actualPort}`,
115
+ close: () => {
116
+ server.close((err) => {
117
+ if (err && err.code !== "ERR_SERVER_NOT_RUNNING") {}
118
+ });
119
+ }
120
+ });
121
+ });
122
+ });
123
+ }
124
+ function handleRequest(req, res, artifactsDir) {
125
+ const url = req.url ?? "/";
126
+ if (url === "/" || url === "/dashboard") {
127
+ serveDashboard(res, artifactsDir);
128
+ return;
129
+ }
130
+ if (url.startsWith("/report/")) {
131
+ let filename;
132
+ try {
133
+ filename = decodeURIComponent(url.slice(8));
134
+ } catch {
135
+ res.writeHead(400, { "content-type": "text/plain" });
136
+ res.end("Invalid URL encoding");
137
+ return;
138
+ }
139
+ serveReport(res, artifactsDir, filename);
140
+ return;
141
+ }
142
+ if (url === "/health") {
143
+ res.writeHead(200, { "content-type": "application/json" });
144
+ res.end(JSON.stringify({ status: "ok" }));
145
+ return;
146
+ }
147
+ res.writeHead(404, { "content-type": "text/plain" });
148
+ res.end("Not found");
149
+ }
150
+ /** List SARIF files and render the dashboard page.
151
+ * All user-controlled data (artifactsDir, filenames) is escaped via
152
+ * escapeHtml() in renderDashboard. CSP blocks inline scripts as
153
+ * defense-in-depth. */
154
+ function serveDashboard(res, artifactsDir) {
155
+ const html = renderDashboard(artifactsDir, listSarifFiles(artifactsDir));
156
+ res.writeHead(200, HTML_HEADERS);
157
+ res.end(html);
158
+ }
159
+ /** Read a SARIF file, normalize findings, and render the HTML report. */
160
+ function serveReport(res, artifactsDir, filename) {
161
+ if (filename === "" || filename.includes("..") || filename.includes("\\") || filename.startsWith("/")) {
162
+ res.writeHead(400, { "content-type": "text/plain" });
163
+ res.end("Invalid filename");
164
+ return;
165
+ }
166
+ const filePath = join(artifactsDir, filename);
167
+ try {
168
+ const realArtifacts = realpathSync(artifactsDir);
169
+ const realFile = realpathSync(filePath);
170
+ if (!realFile.startsWith(realArtifacts + sep) && realFile !== realArtifacts) {
171
+ res.writeHead(400, { "content-type": "text/plain" });
172
+ res.end("Invalid filename");
173
+ return;
174
+ }
175
+ } catch {
176
+ res.writeHead(404, { "content-type": "text/plain" });
177
+ res.end("File not found");
178
+ return;
179
+ }
180
+ try {
181
+ const raw = readFileSync(filePath, "utf8");
182
+ const sarif = JSON.parse(raw);
183
+ const dir = dirname(filename).split(sep).join("/");
184
+ const findings = normalizeSarif(sarif, {
185
+ root: artifactsDir,
186
+ checkIdPrefix: dir === "." ? "" : dir,
187
+ defaultConfidence: .5
188
+ });
189
+ const html = generateSarifHtml(findings);
190
+ res.writeHead(200, reportHeaders(html));
191
+ res.end(html);
192
+ } catch (e) {
193
+ res.writeHead(500, { "content-type": "text/plain" });
194
+ res.end(`Error rendering report: ${e instanceof Error ? e.message : String(e)}`);
195
+ }
196
+ }
197
+ /** Response headers for report pages. The generated report ships inline
198
+ * <script> blocks for filter/search/sort — allow exactly those scripts by
199
+ * SHA-256 hash instead of disabling them with script-src 'none'. */
200
+ function reportHeaders(html) {
201
+ return {
202
+ ...HTML_HEADERS,
203
+ "content-security-policy": `default-src 'self'; script-src ${inlineScriptHashes(html)}; style-src 'unsafe-inline'`
204
+ };
205
+ }
206
+ /** Compute a CSP script-src source list of SHA-256 hashes for every inline
207
+ * <script> block in the HTML. Falls back to 'none' when there are none. */
208
+ function inlineScriptHashes(html) {
209
+ const hashes = [];
210
+ for (const match of html.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script[^>]*>/gi)) {
211
+ const digest = createHash("sha256").update(match[1] ?? "").digest("base64");
212
+ hashes.push(`'sha256-${digest}'`);
213
+ }
214
+ return hashes.length > 0 ? hashes.join(" ") : "'none'";
215
+ }
216
+ /** List .sarif and .sarif.json files under the artifacts directory,
217
+ * recursively. Artifacts are stored as <pipeline>/<step>/<file>.sarif —
218
+ * returned paths are relative to `dir` with forward slashes. */
219
+ function listSarifFiles(dir) {
220
+ if (!existsSync(dir)) return [];
221
+ const files = [];
222
+ collectSarifFiles(dir, dir, files);
223
+ return files.sort((a, b) => a.localeCompare(b));
224
+ }
225
+ function collectSarifFiles(root, dir, out) {
226
+ let entries;
227
+ try {
228
+ entries = readdirSync(dir, { withFileTypes: true });
229
+ } catch {
230
+ return;
231
+ }
232
+ for (const entry of entries) {
233
+ const entryPath = join(dir, entry.name);
234
+ const resolvedEntry = resolve(entryPath);
235
+ if (resolvedEntry !== root && !resolvedEntry.startsWith(root + sep)) continue;
236
+ if (entry.isDirectory()) collectSarifFiles(root, entryPath, out);
237
+ else if (entry.name.endsWith(".sarif") || entry.name.endsWith(".sarif.json")) out.push(relative(root, entryPath).split(sep).join("/"));
238
+ }
239
+ }
240
+ //#endregion
241
+ export { renderDashboard, startUiServer };
242
+
243
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/dashboard.ts","../src/server.ts"],"sourcesContent":["// @sverka/ui — dashboard HTML generator.\n\n/** Render the dashboard page listing available SARIF files. */\nexport function renderDashboard(artifactsDir: string, files: readonly string[]): string {\n const fileList = files.length === 0\n ? '<p class=\"empty\">No SARIF files found. Run <code>sverka run --format sarif</code> to generate findings.</p>'\n : `<ul class=\"file-list\">\n ${files.map((f) => `<li><a href=\"/report/${encodeURIComponent(f)}\">${escapeHtml(f)}</a></li>`).join(\"\\n \")}\n </ul>`;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Sverka Dashboard</title>\n <style>${CSS}</style>\n</head>\n<body>\n <header>\n <h1>Sverka Dashboard</h1>\n <p class=\"path\">Artifacts: <code>${escapeHtml(artifactsDir)}</code></p>\n </header>\n <main>\n <h2>SARIF Reports</h2>\n ${fileList}\n </main>\n <footer>\n <a href=\"https://sverka.dev\">sverka.dev</a>\n </footer>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(text: string): string {\n return text\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#39;\");\n}\n\nconst CSS = String.raw`\n:root { color-scheme: dark; }\n* { box-sizing: border-box; }\nbody {\n margin: 0;\n padding: 0;\n background: #0d1117;\n color: #c9d1d9;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif;\n line-height: 1.6;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n}\nheader {\n padding: 1.5rem 2rem;\n background: #161b22;\n border-bottom: 1px solid #30363d;\n}\nheader h1 { margin: 0 0 0.5rem 0; font-size: 1.5rem; color: #f0f6fc; }\n.path { font-size: 0.875rem; color: #8b949e; margin: 0; }\npath code { color: #58a6ff; }\nmain { flex: 1; padding: 2rem; }\nh2 { font-size: 1.25rem; color: #f0f6fc; margin: 0 0 1rem 0; }\n.file-list { list-style: none; padding: 0; }\n.file-list li { margin-bottom: 0.5rem; }\n.file-list a {\n display: block;\n padding: 0.75rem 1rem;\n background: #161b22;\n border: 1px solid #30363d;\n border-radius: 6px;\n color: #58a6ff;\n text-decoration: none;\n font-family: monospace;\n font-size: 0.9rem;\n}\n.file-list a:hover { border-color: #58a6ff; background: #1c2330; }\n.empty { color: #8b949e; padding: 2rem; text-align: center; }\ncode { background: #21262d; padding: 0.1rem 0.3rem; border-radius: 3px; }\nfooter { padding: 1rem 2rem; border-top: 1px solid #21262d; text-align: center; }\nfooter a { color: #58a6ff; text-decoration: none; }\n`;\n","// @sverka/ui — local web dashboard server.\n\nimport { createServer, type Server, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createHash } from \"node:crypto\";\nimport { readdirSync, readFileSync, existsSync, realpathSync, type Dirent } from \"node:fs\";\nimport { join, dirname, relative, resolve, sep } from \"node:path\";\nimport { normalizeSarif, type Finding, type SarifLog } from \"@sverka/verification\";\nimport { generateSarifHtml } from \"@sverka/sarif-viewer-web\";\nimport { renderDashboard } from \"./dashboard.js\";\n\nexport interface UiServerOptions {\n /** Directory to scan for SARIF files (default: .sverka/artifacts). */\n readonly artifactsDir: string;\n /** Port to listen on (default: 3000). */\n readonly port?: number;\n /** Host to bind (default: localhost). */\n readonly host?: string;\n}\n\nexport interface UiServer {\n readonly port: number;\n readonly url: string;\n close(): void;\n}\n\n/** Security headers for all HTML responses — defense-in-depth against XSS.\n * The CSP blocks inline scripts (script-src 'none') even if escaping fails.\n * generateSarifHtml escapes all user-controlled data via escapeHtml(). */\nconst HTML_HEADERS = {\n \"content-type\": \"text/html; charset=utf-8\",\n \"content-security-policy\": \"default-src 'self'; script-src 'none'; style-src 'unsafe-inline'\",\n \"x-content-type-options\": \"nosniff\",\n} as const;\n\n/**\n * Start a local web dashboard server that lists SARIF files and\n * renders individual findings reports. Returns when the server is\n * listening.\n */\nexport function startUiServer(options: UiServerOptions): Promise<UiServer> {\n const port = options.port ?? 3000;\n const host = options.host ?? \"localhost\";\n const artifactsDir = resolve(options.artifactsDir);\n\n const server: Server = createServer((req: IncomingMessage, res: ServerResponse) => {\n handleRequest(req, res, artifactsDir);\n });\n\n return new Promise((resolve, reject) => {\n const onError = (err: Error): void => reject(err);\n server.on(\"error\", onError);\n\n server.listen(port, host, () => {\n // Get the actual port (supports port 0 = ephemeral).\n const address = server.address();\n const actualPort = typeof address === \"object\" && address ? address.port : port;\n\n // Keep the error listener active for errors after listen (e.g. ECONNRESET).\n server.removeListener(\"error\", onError);\n server.on(\"error\", () => {\n // Swallow post-listen errors — the server is already running.\n });\n\n resolve({\n port: actualPort,\n url: `http://${host}:${actualPort}`,\n close: () => {\n server.close((err) => {\n if (err && (err as NodeJS.ErrnoException).code !== \"ERR_SERVER_NOT_RUNNING\") {\n // Ignore — server may already be closed.\n }\n });\n },\n });\n });\n });\n}\n\nfunction handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n artifactsDir: string,\n): void {\n const url = req.url ?? \"/\";\n\n if (url === \"/\" || url === \"/dashboard\") {\n serveDashboard(res, artifactsDir);\n return;\n }\n\n if (url.startsWith(\"/report/\")) {\n let filename: string;\n try {\n filename = decodeURIComponent(url.slice(\"/report/\".length));\n } catch {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"Invalid URL encoding\");\n return;\n }\n serveReport(res, artifactsDir, filename);\n return;\n }\n\n if (url === \"/health\") {\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({ status: \"ok\" }));\n return;\n }\n\n res.writeHead(404, { \"content-type\": \"text/plain\" });\n res.end(\"Not found\");\n}\n\n/** List SARIF files and render the dashboard page.\n * All user-controlled data (artifactsDir, filenames) is escaped via\n * escapeHtml() in renderDashboard. CSP blocks inline scripts as\n * defense-in-depth. */\nfunction serveDashboard(\n res: ServerResponse,\n artifactsDir: string,\n): void {\n const files = listSarifFiles(artifactsDir);\n const html = renderDashboard(artifactsDir, files);\n res.writeHead(200, HTML_HEADERS);\n res.end(html); // CodeQL: stored XSS — escapeHtml + CSP mitigate\n}\n\n/** Read a SARIF file, normalize findings, and render the HTML report. */\nfunction serveReport(\n res: ServerResponse,\n artifactsDir: string,\n filename: string,\n): void {\n // Validate the relative path — nested step directories are allowed\n // (artifacts are stored as <pipeline>/<step>/<file>.sarif) but\n // traversal, backslashes, and absolute paths are not.\n if (\n filename === \"\" ||\n filename.includes(\"..\") ||\n filename.includes(\"\\\\\") ||\n filename.startsWith(\"/\")\n ) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"Invalid filename\");\n return;\n }\n\n const filePath = join(artifactsDir, filename);\n\n // Resolve real paths and verify the file is inside artifactsDir.\n // This also prevents symlink-based escapes.\n try {\n const realArtifacts = realpathSync(artifactsDir);\n const realFile = realpathSync(filePath);\n if (!realFile.startsWith(realArtifacts + sep) && realFile !== realArtifacts) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"Invalid filename\");\n return;\n }\n } catch {\n res.writeHead(404, { \"content-type\": \"text/plain\" });\n res.end(\"File not found\");\n return;\n }\n\n try {\n const raw = readFileSync(filePath, \"utf8\");\n const sarif = JSON.parse(raw) as SarifLog;\n // Mirror collectFindings: checkId is prefixed with the step directory\n // relative to the artifacts root (\"\" for top-level files).\n const dir = dirname(filename).split(sep).join(\"/\");\n const findings = normalizeSarif(sarif, {\n root: artifactsDir,\n checkIdPrefix: dir === \".\" ? \"\" : dir,\n defaultConfidence: 0.5,\n });\n const html = generateSarifHtml(findings);\n res.writeHead(200, reportHeaders(html));\n res.end(html); // CodeQL: stored XSS — escapeHtml + hash-locked CSP mitigate\n } catch (e) {\n res.writeHead(500, { \"content-type\": \"text/plain\" });\n res.end(`Error rendering report: ${e instanceof Error ? e.message : String(e)}`);\n }\n}\n\n/** Response headers for report pages. The generated report ships inline\n * <script> blocks for filter/search/sort — allow exactly those scripts by\n * SHA-256 hash instead of disabling them with script-src 'none'. */\nfunction reportHeaders(html: string): Record<string, string> {\n return {\n ...HTML_HEADERS,\n \"content-security-policy\": `default-src 'self'; script-src ${inlineScriptHashes(html)}; style-src 'unsafe-inline'`,\n };\n}\n\n/** Compute a CSP script-src source list of SHA-256 hashes for every inline\n * <script> block in the HTML. Falls back to 'none' when there are none. */\nfunction inlineScriptHashes(html: string): string {\n const hashes: string[] = [];\n for (const match of html.matchAll(/<script\\b[^>]*>([\\s\\S]*?)<\\/script[^>]*>/gi)) {\n const digest = createHash(\"sha256\").update(match[1] ?? \"\").digest(\"base64\");\n hashes.push(`'sha256-${digest}'`);\n }\n return hashes.length > 0 ? hashes.join(\" \") : \"'none'\";\n}\n\n/** List .sarif and .sarif.json files under the artifacts directory,\n * recursively. Artifacts are stored as <pipeline>/<step>/<file>.sarif —\n * returned paths are relative to `dir` with forward slashes. */\nfunction listSarifFiles(dir: string): string[] {\n if (!existsSync(dir)) return [];\n const files: string[] = [];\n collectSarifFiles(dir, dir, files);\n return files.sort((a, b) => a.localeCompare(b));\n}\n\nfunction collectSarifFiles(root: string, dir: string, out: string[]): void {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const entryPath = join(dir, entry.name);\n // Prevent traversal outside the artifacts root via ../ or links.\n const resolvedEntry = resolve(entryPath);\n if (resolvedEntry !== root && !resolvedEntry.startsWith(root + sep)) {\n continue;\n }\n if (entry.isDirectory()) {\n collectSarifFiles(root, entryPath, out);\n } else if (entry.name.endsWith(\".sarif\") || entry.name.endsWith(\".sarif.json\")) {\n out.push(relative(root, entryPath).split(sep).join(\"/\"));\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,18 +1,44 @@
1
1
  {
2
- "bugs": "https://github.com/sverka-dev/sverka/issues",
3
- "description": "Placeholder for @sverka/ui published by @nx-devkit/prepare-for-release.",
4
- "homepage": "https://sverka.dev",
5
- "license": "MIT",
6
2
  "name": "@sverka/ui",
7
- "publishConfig": {
8
- "access": "public",
9
- "registry": "https://registry.npmjs.org/"
10
- },
3
+ "version": "0.1.2",
4
+ "description": "Sverka UI — local web dashboard for viewing SARIF findings and run results",
5
+ "license": "MIT",
11
6
  "repository": {
12
7
  "type": "git",
13
8
  "url": "https://github.com/sverka-dev/sverka",
14
9
  "directory": "packages/ui"
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
- "version": "0.0.0"
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
+ "files": [
27
+ "dist"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsdown",
31
+ "test": "vitest run --passWithNoTests",
32
+ "lint": "eslint src",
33
+ "typecheck": "tsc --noEmit"
34
+ },
35
+ "dependencies": {
36
+ "@sverka/verification": "^0.1.2",
37
+ "@sverka/sarif-viewer-web": "^0.1.2"
38
+ },
39
+ "devDependencies": {
40
+ "tsdown": "^0.23.0",
41
+ "typescript": "^5.8.0",
42
+ "vitest": "^5.0.0"
43
+ }
44
+ }