@cyber-dash-tech/revela 0.15.1 → 0.15.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.
- package/lib/edit/prompt.ts +12 -0
- package/lib/inspect/prompt.ts +6 -1
- package/lib/media/download.ts +36 -11
- package/lib/media/save.ts +24 -0
- package/lib/media/search.ts +385 -0
- package/lib/media/types.ts +12 -0
- package/lib/qa/checks.ts +2 -1
- package/lib/qa/index.ts +73 -2
- package/lib/refine/server.ts +758 -68
- package/package.json +1 -1
- package/tools/media-save.ts +6 -0
package/lib/qa/index.ts
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
import { measureSlides } from "./measure"
|
|
10
10
|
import { runChecks, formatReport } from "./checks"
|
|
11
|
-
import type { QAReport } from "./checks"
|
|
11
|
+
import type { LayoutIssue, QAReport } from "./checks"
|
|
12
12
|
import type { DesignClassVocabulary } from "../design/designs"
|
|
13
|
+
import { existsSync, readFileSync } from "fs"
|
|
14
|
+
import { dirname, resolve } from "path"
|
|
13
15
|
|
|
14
16
|
export type { QAReport, SlideReport, LayoutIssue, IssueSeverity } from "./checks"
|
|
15
17
|
export type { RunChecksOptions } from "./checks"
|
|
@@ -34,7 +36,76 @@ export async function runQA(
|
|
|
34
36
|
_vocabulary?: DesignClassVocabulary,
|
|
35
37
|
): Promise<QAReport> {
|
|
36
38
|
const result = await measureSlides(htmlFilePath)
|
|
37
|
-
|
|
39
|
+
const report = runChecks(htmlFilePath, result.slides)
|
|
40
|
+
return withAssetChecks(report, htmlFilePath)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function withAssetChecks(report: QAReport, htmlFilePath: string): QAReport {
|
|
44
|
+
const issues = scanAssetRefs(htmlFilePath)
|
|
45
|
+
if (!issues.length) return report
|
|
46
|
+
const slides = [...report.slides]
|
|
47
|
+
const first = slides[0] ?? { index: 1, title: "Deck", issues: [] }
|
|
48
|
+
slides[0] = { ...first, issues: [...first.issues, ...issues] }
|
|
49
|
+
const errorCount = report.errorCount + issues.filter((issue) => issue.severity === "error").length
|
|
50
|
+
const warningCount = report.warningCount + issues.filter((issue) => issue.severity === "warning").length
|
|
51
|
+
return {
|
|
52
|
+
...report,
|
|
53
|
+
slides,
|
|
54
|
+
totalIssues: report.totalIssues + issues.length,
|
|
55
|
+
errorCount,
|
|
56
|
+
warningCount,
|
|
57
|
+
summary: errorCount === 0
|
|
58
|
+
? `QA passed with ${warningCount} warning${warningCount === 1 ? "" : "s"}.`
|
|
59
|
+
: `QA failed with ${errorCount} error${errorCount === 1 ? "" : "s"} and ${warningCount} warning${warningCount === 1 ? "" : "s"}.`,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function scanAssetRefs(htmlFilePath: string): LayoutIssue[] {
|
|
64
|
+
let html = ""
|
|
65
|
+
try {
|
|
66
|
+
html = readFileSync(htmlFilePath, "utf-8")
|
|
67
|
+
} catch {
|
|
68
|
+
return []
|
|
69
|
+
}
|
|
70
|
+
const refs = new Set<string>()
|
|
71
|
+
const attrPattern = /\b(?:src|href|poster)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi
|
|
72
|
+
let match: RegExpExecArray | null
|
|
73
|
+
while ((match = attrPattern.exec(html))) refs.add(match[1] ?? match[2] ?? match[3] ?? "")
|
|
74
|
+
const cssPattern = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^\s)]+))\s*\)/gi
|
|
75
|
+
while ((match = cssPattern.exec(html))) refs.add(match[1] ?? match[2] ?? match[3] ?? "")
|
|
76
|
+
|
|
77
|
+
const issues: LayoutIssue[] = []
|
|
78
|
+
for (const raw of refs) {
|
|
79
|
+
const ref = raw.trim()
|
|
80
|
+
if (!ref || ref.startsWith("data:") || ref.startsWith("#") || ref.startsWith("mailto:") || ref.startsWith("tel:")) continue
|
|
81
|
+
if (/^https?:\/\//i.test(ref) || ref.startsWith("//")) {
|
|
82
|
+
issues.push({ type: "asset", sub: "remote_url", severity: "error", detail: `Deck HTML references remote asset URL \`${ref}\`. Save network images to workspace assets and reference the local file instead.` })
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
if (ref.includes("/__revela_asset")) {
|
|
86
|
+
issues.push({ type: "asset", sub: "refine_proxy", severity: "error", detail: `Deck HTML references Refine proxy URL \`${ref}\`. Use the saved workspace asset path instead.` })
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
if (!looksLikeImageRef(ref)) continue
|
|
90
|
+
const pathOnly = safeDecode(ref.split(/[?#]/)[0])
|
|
91
|
+
const resolved = resolve(dirname(htmlFilePath), pathOnly)
|
|
92
|
+
if (!existsSync(resolved)) {
|
|
93
|
+
issues.push({ type: "asset", sub: "missing_file", severity: "error", detail: `Deck HTML references missing image asset \`${ref}\`. Use a path relative to the deck HTML file or save the asset into workspace assets first.` })
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return issues
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function looksLikeImageRef(ref: string): boolean {
|
|
100
|
+
return /\.(?:png|jpe?g|webp|gif|svg)(?:[?#].*)?$/i.test(ref)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function safeDecode(value: string): string {
|
|
104
|
+
try {
|
|
105
|
+
return decodeURIComponent(value)
|
|
106
|
+
} catch {
|
|
107
|
+
return value
|
|
108
|
+
}
|
|
38
109
|
}
|
|
39
110
|
|
|
40
111
|
/**
|