verify-demo-output.mjs 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import fs from 'node:fs'
  2. import path from 'node:path'
  3. const outputDir =
  4. process.env.DEMO_OUTPUT_DIR ||
  5. process.env.CLOUDFLARE_PAGES_OUTPUT_DIR ||
  6. 'apps/viewer-demo/dist'
  7. const requiredHtmlEntries = ['index.html', 'compare.html']
  8. const fail = message => {
  9. console.error(`[demo-output] ${message}`)
  10. process.exit(1)
  11. }
  12. const assertFile = filePath => {
  13. if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
  14. fail(`Missing required file: ${filePath}`)
  15. }
  16. }
  17. const readText = filePath => fs.readFileSync(filePath, 'utf8')
  18. const stripQuery = value => value.split(/[?#]/)[0]
  19. const resolveAssetPath = (htmlFile, reference) => {
  20. const normalized = stripQuery(reference)
  21. if (/^(https?:)?\/\//.test(normalized) || normalized.startsWith('data:')) {
  22. return null
  23. }
  24. const baseDir = path.dirname(htmlFile)
  25. return normalized.startsWith('/')
  26. ? path.join(outputDir, normalized.slice(1))
  27. : path.resolve(baseDir, normalized)
  28. }
  29. const collectReferences = html => {
  30. const references = new Set()
  31. const attributePattern = /\b(?:src|href)=["']([^"']+)["']/g
  32. let match
  33. while ((match = attributePattern.exec(html))) {
  34. references.add(match[1])
  35. }
  36. return references
  37. }
  38. for (const entry of requiredHtmlEntries) {
  39. const htmlFile = path.join(outputDir, entry)
  40. assertFile(htmlFile)
  41. const html = readText(htmlFile)
  42. if (entry === 'compare.html' && !html.includes('compare-app')) {
  43. fail('compare.html does not contain the compare app mount node.')
  44. }
  45. if (entry === 'index.html' && !html.includes('id="app"')) {
  46. fail('index.html does not contain the main app mount node.')
  47. }
  48. for (const reference of collectReferences(html)) {
  49. const assetPath = resolveAssetPath(htmlFile, reference)
  50. if (!assetPath) {
  51. continue
  52. }
  53. assertFile(assetPath)
  54. if (/\.(?:js|css)$/i.test(assetPath)) {
  55. const head = readText(assetPath).slice(0, 80).trimStart().toLowerCase()
  56. if (head.startsWith('<!doctype html') || head.startsWith('<html')) {
  57. fail(`Referenced asset resolved to HTML instead of a static asset: ${reference}`)
  58. }
  59. }
  60. }
  61. }
  62. console.log(`[demo-output] Verified ${requiredHtmlEntries.join(', ')} in ${outputDir}`)