@qasshq/qass 0.1.2 → 0.1.3
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/README.md +48 -6
- package/dist/cli.js +264 -5
- package/dist/cli.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +8 -6
- package/dist/core/config.js.map +1 -1
- package/dist/core/doctor.d.ts +12 -0
- package/dist/core/doctor.d.ts.map +1 -0
- package/dist/core/doctor.js +72 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +13 -3
- package/dist/core/runner.js.map +1 -1
- package/dist/core/telemetry.d.ts +44 -0
- package/dist/core/telemetry.d.ts.map +1 -0
- package/dist/core/telemetry.js +131 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/integrations/cursor-rule.js +4 -4
- package/dist/integrations/mcp-server.js +2 -2
- package/dist/integrations/mcp-server.js.map +1 -1
- package/dist/runners/e2e/playwright-runner.d.ts.map +1 -1
- package/dist/runners/e2e/playwright-runner.js +96 -3
- package/dist/runners/e2e/playwright-runner.js.map +1 -1
- package/dist/server/dashboard-server.d.ts +7 -0
- package/dist/server/dashboard-server.d.ts.map +1 -0
- package/dist/server/dashboard-server.js +212 -0
- package/dist/server/dashboard-server.js.map +1 -0
- package/package.json +8 -2
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { loadConfig } from "../core/config.js";
|
|
5
|
+
import { runTests } from "../core/runner.js";
|
|
6
|
+
import { checkLicense } from "../core/license.js";
|
|
7
|
+
import { getTelemetryStatus, readLocalUsage } from "../core/telemetry.js";
|
|
8
|
+
export async function startDashboardServer(opts) {
|
|
9
|
+
let running = false;
|
|
10
|
+
let lastRun = {
|
|
11
|
+
state: "idle",
|
|
12
|
+
};
|
|
13
|
+
const server = createServer(async (req, res) => {
|
|
14
|
+
const sendJson = (status, body) => {
|
|
15
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
16
|
+
res.end(JSON.stringify(body));
|
|
17
|
+
};
|
|
18
|
+
try {
|
|
19
|
+
if (!req.url)
|
|
20
|
+
return sendJson(400, { error: "Invalid request" });
|
|
21
|
+
const url = new URL(req.url, "http://localhost");
|
|
22
|
+
if (url.pathname === "/") {
|
|
23
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
24
|
+
res.end(renderDashboardHtml());
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (url.pathname === "/api/status") {
|
|
28
|
+
const [license, telemetry] = await Promise.all([
|
|
29
|
+
checkLicense(),
|
|
30
|
+
getTelemetryStatus(),
|
|
31
|
+
]);
|
|
32
|
+
return sendJson(200, { license, telemetry, lastRun });
|
|
33
|
+
}
|
|
34
|
+
if (url.pathname === "/api/report") {
|
|
35
|
+
const reportPath = resolve(opts.projectPath, ".qass", "results", "latest.json");
|
|
36
|
+
const raw = await readFile(reportPath, "utf-8").catch(() => "{}");
|
|
37
|
+
return sendJson(200, JSON.parse(raw));
|
|
38
|
+
}
|
|
39
|
+
if (url.pathname === "/api/usage") {
|
|
40
|
+
const usage = await readLocalUsage(opts.projectPath);
|
|
41
|
+
return sendJson(200, usage ?? { message: "No usage data yet." });
|
|
42
|
+
}
|
|
43
|
+
if (url.pathname === "/api/business") {
|
|
44
|
+
const [license, usage, telemetry, reportRaw] = await Promise.all([
|
|
45
|
+
checkLicense(),
|
|
46
|
+
readLocalUsage(opts.projectPath),
|
|
47
|
+
getTelemetryStatus(),
|
|
48
|
+
readFile(resolve(opts.projectPath, ".qass", "results", "latest.json"), "utf-8").catch(() => "{}"),
|
|
49
|
+
]);
|
|
50
|
+
const report = JSON.parse(reportRaw);
|
|
51
|
+
const runs = usage?.totals?.runs ?? 0;
|
|
52
|
+
const success = usage?.totals?.success ?? 0;
|
|
53
|
+
const failed = usage?.totals?.failed ?? 0;
|
|
54
|
+
const successRate = runs > 0 ? Number(((success / runs) * 100).toFixed(1)) : 0;
|
|
55
|
+
const automationCoverage = runs > 0
|
|
56
|
+
? Number(((((usage?.commands?.scan?.runs ?? 0) + (usage?.commands?.test?.runs ?? 0)) / runs) *
|
|
57
|
+
100).toFixed(1))
|
|
58
|
+
: 0;
|
|
59
|
+
const high = report.securityFindings?.filter((f) => f.severity === "HIGH").length ?? 0;
|
|
60
|
+
const medium = report.securityFindings?.filter((f) => f.severity === "MEDIUM").length ?? 0;
|
|
61
|
+
const low = report.securityFindings?.filter((f) => f.severity === "LOW").length ?? 0;
|
|
62
|
+
return sendJson(200, {
|
|
63
|
+
plan: license.plan,
|
|
64
|
+
validLicense: license.valid,
|
|
65
|
+
totalRuns: runs,
|
|
66
|
+
successfulRuns: success,
|
|
67
|
+
failedRuns: failed,
|
|
68
|
+
successRate,
|
|
69
|
+
automationCoverage,
|
|
70
|
+
findingsPressure: { high, medium, low },
|
|
71
|
+
endpointConfigured: telemetry.endpointConfigured,
|
|
72
|
+
summary: {
|
|
73
|
+
passedTests: report.summary?.passed ?? 0,
|
|
74
|
+
failedTests: report.summary?.failed ?? 0,
|
|
75
|
+
totalFindings: report.summary?.securityFindings ?? 0,
|
|
76
|
+
},
|
|
77
|
+
recommendations: [
|
|
78
|
+
automationCoverage < 70
|
|
79
|
+
? "Increase automated scan/test usage after each change."
|
|
80
|
+
: "Automation coverage is strong.",
|
|
81
|
+
high > 0
|
|
82
|
+
? "Resolve HIGH findings before release."
|
|
83
|
+
: "No HIGH security findings detected.",
|
|
84
|
+
!telemetry.endpointConfigured
|
|
85
|
+
? "Set QASS_TELEMETRY_ENDPOINT for centralized team/business dashboards."
|
|
86
|
+
: "Remote telemetry endpoint configured.",
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (url.pathname === "/api/screenshots") {
|
|
91
|
+
const shotDir = resolve(opts.projectPath, ".qass", "results", "screenshots");
|
|
92
|
+
const files = await readdir(shotDir).catch(() => []);
|
|
93
|
+
return sendJson(200, files.map((f) => ({ name: f, path: join(".qass", "results", "screenshots", f) })));
|
|
94
|
+
}
|
|
95
|
+
if (url.pathname === "/api/run" && req.method === "POST") {
|
|
96
|
+
if (running)
|
|
97
|
+
return sendJson(409, { error: "A run is already in progress." });
|
|
98
|
+
const chunks = [];
|
|
99
|
+
for await (const chunk of req)
|
|
100
|
+
chunks.push(Buffer.from(chunk));
|
|
101
|
+
const payload = chunks.length
|
|
102
|
+
? JSON.parse(Buffer.concat(chunks).toString("utf-8"))
|
|
103
|
+
: {};
|
|
104
|
+
const type = payload.type === "scan" ? "security" : "all";
|
|
105
|
+
running = true;
|
|
106
|
+
lastRun = { state: "running", type: payload.type ?? "test", startedAt: new Date().toISOString() };
|
|
107
|
+
try {
|
|
108
|
+
const config = await loadConfig(opts.projectPath);
|
|
109
|
+
await runTests(config, opts.projectPath, payload.diff ?? "HEAD", type, Boolean(payload.full));
|
|
110
|
+
lastRun = { state: "idle", type: payload.type ?? "test", startedAt: new Date().toISOString() };
|
|
111
|
+
return sendJson(200, { ok: true });
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
running = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
sendJson(404, { error: "Not found" });
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
sendJson(500, { error: e instanceof Error ? e.message : "Unknown error" });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
await new Promise((resolveStart) => {
|
|
124
|
+
server.listen(opts.port, "127.0.0.1", () => resolveStart());
|
|
125
|
+
});
|
|
126
|
+
return server;
|
|
127
|
+
}
|
|
128
|
+
function renderDashboardHtml() {
|
|
129
|
+
return `<!doctype html>
|
|
130
|
+
<html>
|
|
131
|
+
<head>
|
|
132
|
+
<meta charset="utf-8" />
|
|
133
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
134
|
+
<title>QASS Dashboard</title>
|
|
135
|
+
<style>
|
|
136
|
+
body { font-family: Inter, Arial, sans-serif; background:#0b0b0b; color:#e8e8e8; margin:0; }
|
|
137
|
+
.wrap { max-width: 1100px; margin: 0 auto; padding: 24px; }
|
|
138
|
+
.row { display:flex; gap:12px; flex-wrap:wrap; margin-bottom:16px; }
|
|
139
|
+
button { background:#00ff88; border:none; color:#000; padding:10px 14px; border-radius:8px; font-weight:700; cursor:pointer; }
|
|
140
|
+
.card { background:#111; border:1px solid #222; border-radius:12px; padding:16px; margin-bottom:12px; }
|
|
141
|
+
.kpis { display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:10px; margin-bottom:12px; }
|
|
142
|
+
.kpi { background:#0f0f0f; border:1px solid #202020; border-radius:10px; padding:10px; }
|
|
143
|
+
.kpi .label { color:#8f8f8f; font-size:11px; text-transform:uppercase; letter-spacing:.5px; }
|
|
144
|
+
.kpi .value { font-size:22px; font-weight:800; margin-top:4px; }
|
|
145
|
+
.green { color:#00ff88; }
|
|
146
|
+
.yellow { color:#ffcc66; }
|
|
147
|
+
.red { color:#ff6666; }
|
|
148
|
+
pre { white-space:pre-wrap; word-break:break-word; color:#bdbdbd; }
|
|
149
|
+
.muted { color:#888; font-size:12px; }
|
|
150
|
+
</style>
|
|
151
|
+
</head>
|
|
152
|
+
<body>
|
|
153
|
+
<div class="wrap">
|
|
154
|
+
<h1>QASS Dashboard</h1>
|
|
155
|
+
<p class="muted">Automation-first CLI with visual observability.</p>
|
|
156
|
+
<div class="row">
|
|
157
|
+
<button onclick="run('scan')">Run Security Scan</button>
|
|
158
|
+
<button onclick="run('test')">Run Full Test</button>
|
|
159
|
+
<button onclick="refreshAll()">Refresh</button>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="card">
|
|
162
|
+
<h3>Business Snapshot</h3>
|
|
163
|
+
<div class="kpis">
|
|
164
|
+
<div class="kpi"><div class="label">Plan</div><div class="value" id="kpiPlan">-</div></div>
|
|
165
|
+
<div class="kpi"><div class="label">Runs</div><div class="value" id="kpiRuns">0</div></div>
|
|
166
|
+
<div class="kpi"><div class="label">Success Rate</div><div class="value green" id="kpiSuccess">0%</div></div>
|
|
167
|
+
<div class="kpi"><div class="label">Automation Coverage</div><div class="value yellow" id="kpiAuto">0%</div></div>
|
|
168
|
+
<div class="kpi"><div class="label">Open High Findings</div><div class="value red" id="kpiHigh">0</div></div>
|
|
169
|
+
<div class="kpi"><div class="label">Failed Tests</div><div class="value red" id="kpiFailed">0</div></div>
|
|
170
|
+
</div>
|
|
171
|
+
<pre id="business"></pre>
|
|
172
|
+
</div>
|
|
173
|
+
<div class="card"><h3>Status</h3><pre id="status"></pre></div>
|
|
174
|
+
<div class="card"><h3>Latest Report</h3><pre id="report"></pre></div>
|
|
175
|
+
<div class="card"><h3>Usage</h3><pre id="usage"></pre></div>
|
|
176
|
+
<div class="card"><h3>Screenshots</h3><pre id="shots"></pre></div>
|
|
177
|
+
</div>
|
|
178
|
+
<script>
|
|
179
|
+
async function get(path) {
|
|
180
|
+
const res = await fetch(path);
|
|
181
|
+
return await res.json();
|
|
182
|
+
}
|
|
183
|
+
async function run(type) {
|
|
184
|
+
await fetch('/api/run', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ type }) });
|
|
185
|
+
await refreshAll();
|
|
186
|
+
}
|
|
187
|
+
async function refreshAll() {
|
|
188
|
+
const [status, report, usage, shots, business] = await Promise.all([
|
|
189
|
+
get('/api/status'),
|
|
190
|
+
get('/api/report'),
|
|
191
|
+
get('/api/usage'),
|
|
192
|
+
get('/api/screenshots'),
|
|
193
|
+
get('/api/business')
|
|
194
|
+
]);
|
|
195
|
+
document.getElementById('kpiPlan').textContent = String(business.plan || '-').toUpperCase();
|
|
196
|
+
document.getElementById('kpiRuns').textContent = String(business.totalRuns || 0);
|
|
197
|
+
document.getElementById('kpiSuccess').textContent = String((business.successRate || 0) + '%');
|
|
198
|
+
document.getElementById('kpiAuto').textContent = String((business.automationCoverage || 0) + '%');
|
|
199
|
+
document.getElementById('kpiHigh').textContent = String(business.findingsPressure?.high || 0);
|
|
200
|
+
document.getElementById('kpiFailed').textContent = String(business.summary?.failedTests || 0);
|
|
201
|
+
document.getElementById('business').textContent = JSON.stringify(business, null, 2);
|
|
202
|
+
document.getElementById('status').textContent = JSON.stringify(status, null, 2);
|
|
203
|
+
document.getElementById('report').textContent = JSON.stringify(report, null, 2);
|
|
204
|
+
document.getElementById('usage').textContent = JSON.stringify(usage, null, 2);
|
|
205
|
+
document.getElementById('shots').textContent = JSON.stringify(shots, null, 2);
|
|
206
|
+
}
|
|
207
|
+
refreshAll();
|
|
208
|
+
</script>
|
|
209
|
+
</body>
|
|
210
|
+
</html>`;
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=dashboard-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-server.js","sourceRoot":"","sources":["../../src/server/dashboard-server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qasshq/qass",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "QA + Security Scanner for vibe-coded applications. Your AI writes code. QASS catches what it got wrong.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
"dev": "tsx src/cli.ts",
|
|
16
16
|
"build": "tsc",
|
|
17
17
|
"start": "node dist/cli.js",
|
|
18
|
-
"test": "vitest run"
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"ui": "tsx src/cli.ts ui --project .",
|
|
20
|
+
"doctor": "tsx src/cli.ts doctor --project .",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
19
25
|
},
|
|
20
26
|
"keywords": [
|
|
21
27
|
"qa",
|