@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/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
- return runChecks(htmlFilePath, result.slides)
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
  /**