create-claude-cabinet 0.29.4 → 0.29.6
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/package.json +1 -1
- package/templates/site-audit-runtime/package.json +1 -1
- package/templates/site-audit-runtime/src/checks/axe-core.mjs +2 -2
- package/templates/site-audit-runtime/src/checks/blacklight.mjs +2 -2
- package/templates/site-audit-runtime/src/checks/lighthouse.mjs +3 -3
- package/templates/site-audit-runtime/src/checks/linkinator.mjs +2 -2
- package/templates/site-audit-runtime/src/checks/observatory.mjs +2 -2
- package/templates/site-audit-runtime/src/checks/pa11y.mjs +2 -2
- package/templates/site-audit-runtime/src/checks/unlighthouse.mjs +3 -3
- package/templates/site-audit-runtime/src/cli.mjs +1 -1
- package/templates/site-audit-runtime/src/orchestrator.mjs +5 -2
- package/templates/site-audit-runtime/src/report.mjs +15 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-cabinet/site-audit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Comprehensive deployed-site quality audit engine for Claude Cabinet. Runs checks across performance, accessibility, security, SEO, content, DNS, and privacy against a deployed URL; single-site and comparison modes; standalone HTML report.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -2,12 +2,12 @@ export const checkId = 'axe-core';
|
|
|
2
2
|
export const tool = 'axe-core (WCAG AA)';
|
|
3
3
|
|
|
4
4
|
export async function detect(executor) {
|
|
5
|
-
const r = await executor.spawn('
|
|
5
|
+
const r = await executor.spawn('axe', ['--version'], { timeoutMs: 15_000 });
|
|
6
6
|
return r.code === 0;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export async function run(url, executor) {
|
|
10
|
-
return executor.spawn('
|
|
10
|
+
return executor.spawn('axe', [url, '--exit'], { timeoutMs: 60_000 });
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const IMPACT_TO_SEVERITY = { critical: 'critical', serious: 'serious', moderate: 'moderate', minor: 'info' };
|
|
@@ -7,12 +7,12 @@ export const tool = 'Blacklight (tracker detection)';
|
|
|
7
7
|
export const defaultTimeoutMs = 120_000;
|
|
8
8
|
|
|
9
9
|
export async function detect(executor) {
|
|
10
|
-
const r = await executor.spawn('
|
|
10
|
+
const r = await executor.spawn('blacklight-collector', ['--help'], { timeoutMs: 15_000 });
|
|
11
11
|
return r.code === 0 || r.stdout.includes('blacklight');
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export async function run(url, executor) {
|
|
15
|
-
return executor.spawn('
|
|
15
|
+
return executor.spawn('blacklight-collector', [url, '--json'], { timeoutMs: 120_000 });
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const TRACKER_SEVERITY = {
|
|
@@ -3,13 +3,13 @@ export const tool = 'Lighthouse';
|
|
|
3
3
|
export const defaultTimeoutMs = 120_000;
|
|
4
4
|
|
|
5
5
|
export async function detect(executor) {
|
|
6
|
-
const r = await executor.spawn('
|
|
6
|
+
const r = await executor.spawn('lighthouse', ['--version'], { timeoutMs: 15_000 });
|
|
7
7
|
return r.code === 0;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export async function run(url, executor) {
|
|
11
|
-
const r = await executor.spawn('
|
|
12
|
-
|
|
11
|
+
const r = await executor.spawn('lighthouse', [
|
|
12
|
+
url,
|
|
13
13
|
'--output=json',
|
|
14
14
|
'--chrome-flags=--headless=new --no-sandbox',
|
|
15
15
|
'--only-categories=performance,accessibility,best-practices,seo',
|
|
@@ -2,12 +2,12 @@ export const checkId = 'linkinator';
|
|
|
2
2
|
export const tool = 'Linkinator (broken links)';
|
|
3
3
|
|
|
4
4
|
export async function detect(executor) {
|
|
5
|
-
const r = await executor.spawn('
|
|
5
|
+
const r = await executor.spawn('linkinator', ['--version'], { timeoutMs: 15_000 });
|
|
6
6
|
return r.code === 0;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export async function run(url, executor) {
|
|
10
|
-
return executor.spawn('
|
|
10
|
+
return executor.spawn('linkinator', [url, '--format', 'json', '--timeout', '10000'], { timeoutMs: 60_000 });
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function normalize(raw, durationMs) {
|
|
@@ -2,13 +2,13 @@ export const checkId = 'observatory';
|
|
|
2
2
|
export const tool = 'MDN HTTP Observatory';
|
|
3
3
|
|
|
4
4
|
export async function detect(executor) {
|
|
5
|
-
const r = await executor.spawn('
|
|
5
|
+
const r = await executor.spawn('mdn-http-observatory', ['--version'], { timeoutMs: 15_000 });
|
|
6
6
|
return r.code === 0;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export async function run(url, executor) {
|
|
10
10
|
const hostname = new URL(url).hostname;
|
|
11
|
-
return executor.spawn('
|
|
11
|
+
return executor.spawn('mdn-http-observatory', [hostname], { timeoutMs: 60_000 });
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const GRADE_SCORES = { 'A+': 100, 'A': 95, 'A-': 90, 'B+': 85, 'B': 80, 'B-': 75, 'C+': 70, 'C': 65, 'C-': 60, 'D+': 55, 'D': 50, 'D-': 45, 'F': 20 };
|
|
@@ -2,12 +2,12 @@ export const checkId = 'pa11y';
|
|
|
2
2
|
export const tool = 'Pa11y (WCAG AAA)';
|
|
3
3
|
|
|
4
4
|
export async function detect(executor) {
|
|
5
|
-
const r = await executor.spawn('
|
|
5
|
+
const r = await executor.spawn('pa11y', ['--version'], { timeoutMs: 15_000 });
|
|
6
6
|
return r.code === 0;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export async function run(url, executor) {
|
|
10
|
-
return executor.spawn('
|
|
10
|
+
return executor.spawn('pa11y', [url, '--reporter', 'json', '--standard', 'WCAG2AAA'], { timeoutMs: 60_000 });
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const TYPE_TO_SEVERITY = { error: 'serious', warning: 'moderate', notice: 'info' };
|
|
@@ -7,13 +7,13 @@ export const tool = 'Unlighthouse (full-site crawl)';
|
|
|
7
7
|
export const defaultTimeoutMs = 300_000;
|
|
8
8
|
|
|
9
9
|
export async function detect(executor) {
|
|
10
|
-
const r = await executor.spawn('
|
|
10
|
+
const r = await executor.spawn('unlighthouse', ['--version'], { timeoutMs: 15_000 });
|
|
11
11
|
return r.code === 0;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export async function run(url, executor) {
|
|
15
|
-
return executor.spawn('
|
|
16
|
-
'
|
|
15
|
+
return executor.spawn('unlighthouse', [
|
|
16
|
+
'--site', url, '--ci', '--reporter', 'json',
|
|
17
17
|
], { timeoutMs: 300_000 });
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -85,7 +85,7 @@ export function validateOptions(opts) {
|
|
|
85
85
|
function writeReport(html, report, outDir, suffix = '') {
|
|
86
86
|
const dir = outDir || 'reports';
|
|
87
87
|
mkdirSync(dir, { recursive: true });
|
|
88
|
-
const host = sanitizeHostname(report.url);
|
|
88
|
+
const host = sanitizeHostname(report.url || report.urlA || 'unknown');
|
|
89
89
|
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
90
90
|
let base = `site-audit-${host}-${ts}${suffix ? '-' + suffix : ''}`;
|
|
91
91
|
let path = join(dir, `${base}.html`);
|
|
@@ -180,13 +180,16 @@ export async function auditSite(url, checks, opts = {}) {
|
|
|
180
180
|
|
|
181
181
|
const start = Date.now();
|
|
182
182
|
let available;
|
|
183
|
+
let detectError = '';
|
|
183
184
|
try {
|
|
184
185
|
available = await check.detect(executor);
|
|
185
|
-
} catch {
|
|
186
|
+
} catch (err) {
|
|
186
187
|
available = false;
|
|
188
|
+
detectError = err instanceof Error ? err.message : String(err);
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
if (!available) {
|
|
192
|
+
const hint = detectError ? ` (${detectError})` : ' (binary not found in PATH)';
|
|
190
193
|
return {
|
|
191
194
|
checkId: check.checkId,
|
|
192
195
|
tool: check.tool,
|
|
@@ -196,7 +199,7 @@ export async function auditSite(url, checks, opts = {}) {
|
|
|
196
199
|
severity: null,
|
|
197
200
|
findings: [],
|
|
198
201
|
durationMs: Date.now() - start,
|
|
199
|
-
reason: `${check.tool} not available`,
|
|
202
|
+
reason: `${check.tool} not available${hint}`,
|
|
200
203
|
};
|
|
201
204
|
}
|
|
202
205
|
|
|
@@ -137,10 +137,12 @@ export function generateSummary(delta) {
|
|
|
137
137
|
const biggest = sorted[0];
|
|
138
138
|
|
|
139
139
|
let summary = '';
|
|
140
|
+
const sA = hostnameLabel(delta.urlA);
|
|
141
|
+
const sB = hostnameLabel(delta.urlB);
|
|
140
142
|
if (bWins > aWins) {
|
|
141
|
-
summary +=
|
|
143
|
+
summary += `${esc(sB)} outperforms ${esc(sA)} on ${bWins} of ${scored.length} scored dimension${scored.length > 1 ? 's' : ''}`;
|
|
142
144
|
} else if (aWins > bWins) {
|
|
143
|
-
summary +=
|
|
145
|
+
summary += `${esc(sA)} outperforms ${esc(sB)} on ${aWins} of ${scored.length} scored dimension${scored.length > 1 ? 's' : ''}`;
|
|
144
146
|
} else {
|
|
145
147
|
summary += `Sites are evenly matched across ${scored.length} scored dimension${scored.length > 1 ? 's' : ''}`;
|
|
146
148
|
}
|
|
@@ -236,7 +238,13 @@ function classifyFindings(a, b) {
|
|
|
236
238
|
* @param {import('./diff.mjs').DeltaReport} delta
|
|
237
239
|
* @returns {string}
|
|
238
240
|
*/
|
|
241
|
+
function hostnameLabel(url) {
|
|
242
|
+
try { return new URL(url).hostname; } catch { return url; }
|
|
243
|
+
}
|
|
244
|
+
|
|
239
245
|
export function renderComparison(delta) {
|
|
246
|
+
const labelA = hostnameLabel(delta.urlA);
|
|
247
|
+
const labelB = hostnameLabel(delta.urlB);
|
|
240
248
|
const title = `Site Comparison — ${delta.urlA} vs ${delta.urlB}`;
|
|
241
249
|
|
|
242
250
|
let html = head(title);
|
|
@@ -248,14 +256,14 @@ export function renderComparison(delta) {
|
|
|
248
256
|
const { aOnly, bOnly } = delta.summary;
|
|
249
257
|
if (aOnly > 0 || bOnly > 0) {
|
|
250
258
|
html += `<div class="asymmetric-warning">Asymmetric availability: `;
|
|
251
|
-
if (aOnly > 0) html += `${aOnly} check${aOnly > 1 ? 's' : ''} ran only for
|
|
252
|
-
if (bOnly > 0) html += `${bOnly} check${bOnly > 1 ? 's' : ''} ran only for
|
|
259
|
+
if (aOnly > 0) html += `${aOnly} check${aOnly > 1 ? 's' : ''} ran only for ${esc(labelA)}. `;
|
|
260
|
+
if (bOnly > 0) html += `${bOnly} check${bOnly > 1 ? 's' : ''} ran only for ${esc(labelB)}. `;
|
|
253
261
|
html += `Deltas for one-sided checks show N/A.</div>`;
|
|
254
262
|
}
|
|
255
263
|
|
|
256
264
|
// ── Comparison grid with drill-down links ──
|
|
257
265
|
html += '<div style="background:#fff;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,.05);overflow:hidden;margin-bottom:2rem">';
|
|
258
|
-
html += `<div class="compare-row"><span>Check</span><span
|
|
266
|
+
html += `<div class="compare-row"><span>Check</span><span>${esc(labelA)}</span><span>${esc(labelB)}</span><span>Delta</span></div>`;
|
|
259
267
|
|
|
260
268
|
for (const d of delta.deltas) {
|
|
261
269
|
const aDisplay = d.a && d.a.status !== 'skip'
|
|
@@ -308,8 +316,8 @@ export function renderComparison(delta) {
|
|
|
308
316
|
if (d.availability === 'both') {
|
|
309
317
|
const { shared, aOnly: aOnlyF, bOnly: bOnlyF } = classifyFindings(d.a, d.b);
|
|
310
318
|
html += '<div class="side-by-side">';
|
|
311
|
-
html += `<div class="site-column"><h4
|
|
312
|
-
html += `<div class="site-column"><h4
|
|
319
|
+
html += `<div class="site-column"><h4>${esc(labelA)}</h4>${compareCardFindings(esc(labelA) + ' only', aOnlyF, d.a)}</div>`;
|
|
320
|
+
html += `<div class="site-column"><h4>${esc(labelB)}</h4>${compareCardFindings(esc(labelB) + ' only', bOnlyF, d.b)}</div>`;
|
|
313
321
|
html += '</div>';
|
|
314
322
|
if (shared.length) {
|
|
315
323
|
html += `<div class="finding-group-label" style="margin-top:1rem">Shared issues (both sites)</div>${renderFindings(shared)}`;
|