← Files DXIRARCHIVED FILE

scripts/document.mjs

8.21 KB · Nov 7, 2026 · 06:12 UTC

↓ Download file

import c from"node:fs";import L from"node:os";import n from"node:path";import{createHash as F}from"node:crypto";import{spawn as j}from"node:child_process";import{pathToFileURL as R}from"node:url";import{DOCUMENT_LIMIT as S,DOCUMENT_VERSION as M,renderDocument as N}from"./document-template.mjs";const b=e=>F("sha256").update(e).digest("hex"),s=(e,t)=>{if(!e)throw new Error(t)};function f(e,t,r=!0){const i=c.realpathSync(t);s(typeof e=="string"&&e.length>0&&!n.isAbsolute(e),"Use a project-relative path.");const o=n.resolve(i,e),a=r?c.realpathSync(o):n.join(c.realpathSync(n.dirname(o)),n.basename(o)),l=n.relative(i,a);return s(l&&l!==".."&&!l.startsWith(`..${n.sep}`)&&!n.isAbsolute(l),"Keep document files inside the selected project."),r||s(!c.existsSync(a),"Output already exists. Preserve reserved files; choose a new versioned filename."),a}function T({input:e,output:t,cwd:r=process.cwd()}){const i=f(e,r);s(c.statSync(i).size<=S,"Document input exceeds 8 MiB."),s(/\.html$/i.test(t),"Save the document as .html.");const o=f(t,r,!1),a=N(JSON.parse(c.readFileSync(i,"utf8").replace(/^\uFEFF/,"")));return c.writeFileSync(o,a,{encoding:"utf8",flag:"wx"}),{relative_path:t,content_type:"text/html",size_bytes:Buffer.byteLength(a),sha256:b(a),print_checked:!1}}function I(e=process.env.DXIR_PRINT_BROWSER){const r=(e?[e]:[process.env.PROGRAMFILES&&n.join(process.env.PROGRAMFILES,"Google/Chrome/Application/chrome.exe"),process.env["PROGRAMFILES(X86)"]&&n.join(process.env["PROGRAMFILES(X86)"],"Microsoft/Edge/Application/msedge.exe"),process.env.PROGRAMFILES&&n.join(process.env.PROGRAMFILES,"Microsoft/Edge/Application/msedge.exe"),process.env.LOCALAPPDATA&&n.join(process.env.LOCALAPPDATA,"Google/Chrome/Application/chrome.exe"),"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge","/usr/bin/chromium","/usr/bin/chromium-browser","/usr/bin/google-chrome","/usr/bin/microsoft-edge"]).find(i=>i&&c.existsSync(i)&&c.statSync(i).isFile());return s(r,"Install Edge/Chrome or set DXIR_PRINT_BROWSER to its executable. HTML exists, but printing is not verified."),r}const k=`async function inspectDocument(){
  if (document.readyState !== 'complete') await new Promise(resolve => window.addEventListener('load', resolve, {once:true}));
  await document.fonts.ready;
  const issues = [];
  for (const [index, image] of Array.from(document.images).entries()) if (!image.complete || !image.naturalWidth || !image.naturalHeight) issues.push('image_' + (index + 1) + '_unavailable');
  const slides = Array.from(document.querySelectorAll('.slide'));
  for (const [index, slide] of slides.entries()) {
    const box = slide.getBoundingClientRect();
    const body = slide.querySelector('.slide-body');
    if (!body || body.getBoundingClientRect().bottom > box.bottom - parseFloat(getComputedStyle(slide).paddingBottom) + 1 || slide.scrollWidth > slide.clientWidth + 1) issues.push('slide_' + (index + 1) + '_overflow');
  }
  const paper = document.querySelector('.document');
  if (paper) {
    if (paper.scrollWidth > paper.clientWidth + 1) issues.push('document_horizontal_overflow');
    for (const [index, row] of Array.from(paper.querySelectorAll('tr')).entries()) if (row.getBoundingClientRect().height > 261 * 96 / 25.4) issues.push('table_row_' + (index + 1) + '_exceeds_page');
  }
  document.body.setAttribute('data-action-room-check', btoa(JSON.stringify({issues, slides: slides.length})));
}
inspectDocument().catch(() => document.body.setAttribute('data-action-room-check', btoa(JSON.stringify({issues:['layout_check_failed'],slides:0}))));`;function $(e,t,r){const i=e.toString("latin1");s(i.startsWith("%PDF-"),"Browser did not produce a PDF.");const o=[...i.matchAll(/\/Type\s*\/Page\b/g)].length,a=[...i.matchAll(/\/MediaBox\s*\[\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s*\]/g)];s(o>0&&a.length===o,"PDF page geometry could not be verified.");const l=t==="presentation";for(const d of a){const m=Number(d[3])-Number(d[1]),u=Number(d[4])-Number(d[2]);s(Math.abs(m-(l?841.89:595.28))<2&&Math.abs(u-(l?595.28:841.89))<2,"PDF paper size or orientation is incorrect.")}return s(!l||o===r,"Each slide must occupy exactly one PDF page. Split overflowing slides and check again."),o}function W(e,t){return new Promise((r,i)=>{const o=j(e,t,{windowsHide:!0,stdio:["ignore","pipe","ignore"]}),a=[];let l=0,d=!1;const m=p=>{d||(d=!0,o.kill(),i(new Error(p)))},u=setTimeout(()=>m("Browser print check timed out. Printing remains unverified."),6e4);o.stdout.on("data",p=>{l+=p.length,l>S*3?m("Browser output exceeded the print-check limit."):a.push(p)}),o.once("error",()=>{clearTimeout(u),m("Could not start the print browser.")}),o.once("close",p=>{clearTimeout(u),d||(p===0?r(Buffer.concat(a).toString("utf8")):i(new Error("Browser print check failed.")))})})}async function G({file:e,pdf:t,evidence:r,browser:i,supportDir:o,cwd:a=process.cwd()}){const l=f(e,a);s(c.statSync(l).size<=S,"HTML exceeds 8 MiB.");const d=c.readFileSync(l),m=d.toString("utf8");s(m.includes(`data-action-room-document="${M}"`),"Use the Action Room HTML template before checking the document.");const u=/data-action-room-layout="(document|presentation)"/.exec(m)?.[1];s(u,"Document layout is missing.");const p=t?f(t,a,!1):null,v=r?f(r,a,!1):null;s(!t||/\.pdf$/i.test(t),"Use a .pdf output path."),s(!r||/\.json$/i.test(r),"Use a .json evidence path.");const E=I(i),w=o?f(o,a):L.tmpdir();s(c.statSync(w).isDirectory(),"Print support path must be a directory.");const h=c.mkdtempSync(n.join(w,"dxir-document-print-"));try{const O=`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-${F("sha256").update(k).digest("base64")}'; style-src 'unsafe-inline'; img-src data:; font-src data:; base-uri 'none'; form-action 'none'">`,B=m.replace(/<meta\b[^>]*http-equiv=["']Content-Security-Policy["'][^>]*>/gi,"").replace(/<script\b[^>]*>[\s\S]*?<\/script\s*>/gi,"").replace(/<head>/i,`<head>${O}`).replace(/<\/body>/i,`<script>${k}<\/script></body>`),x=n.join(h,"document.html"),A=n.join(h,"document.pdf");c.writeFileSync(x,B);const C=await W(E,["--headless","--disable-gpu","--no-first-run","--no-default-browser-check","--disable-extensions","--disable-background-networking",`--user-data-dir=${n.join(h,"profile")}`,"--window-size=1440,1100","--virtual-time-budget=2000","--no-pdf-header-footer",`--print-to-pdf=${A}`,"--dump-dom",R(x).href]),_=/data-action-room-check="([A-Za-z0-9+/=]+)"/.exec(C)?.[1];s(_,"Browser did not return a layout check. Printing remains unverified.");const g=JSON.parse(Buffer.from(_,"base64").toString("utf8"));s(Array.isArray(g.issues)&&g.issues.length===0,`Print layout needs correction: ${(g.issues||[]).join(", ")}. Preserve content and split long slides/rows.`),s(u!=="presentation"||g.slides>0,"Presentation has no slides.");const y=c.readFileSync(A),D=$(y,u,g.slides);s(!p||y.length<=S,"PDF exceeds 8 MiB. Reduce images or split the deliverable."),s(b(c.readFileSync(l))===b(d),"HTML changed during verification. Check the current file again.");const P={version:M,relative_path:e,sha256:b(d),size_bytes:d.length,layout:u,paper:"A4",orientation:u==="presentation"?"landscape":"portrait",pages:D,browser_layout_checked:!0,pdf_geometry_checked:!0,visual_review_required:!0,...p?{pdf:{relative_path:t,sha256:b(y),size_bytes:y.length,content_type:"application/pdf"}}:{}};return p&&c.writeFileSync(p,y,{flag:"wx"}),v&&c.writeFileSync(v,JSON.stringify(P,null,2)+`
`,{flag:"wx"}),P}finally{s(n.dirname(n.resolve(h))===n.resolve(w)&&n.basename(h).startsWith("dxir-document-print-"),"Unsafe temporary path."),c.rmSync(h,{recursive:!0,force:!0,maxRetries:5,retryDelay:150})}}async function U(){const[e,...t]=process.argv.slice(2),r={};for(let o=0;o<t.length;o+=2)s(t[o].startsWith("--")&&t[o+1]!==void 0,"Use named options with values."),r[t[o].slice(2)]=t[o+1];s(["build","check"].includes(e),"Use document.mjs build or check.");const i=e==="build"?T(r):await G(r);process.stdout.write(JSON.stringify(i)+`
`)}process.argv[1]&&import.meta.url===R(n.resolve(process.argv[1])).href&&U().catch(e=>{const t=e instanceof SyntaxError||e.code?"Check the input JSON and project-relative paths; existing outputs were preserved.":e.message;process.stderr.write(`${t}
`),process.exitCode=1});export{T as buildDocument,G as checkDocument,I as findPrintBrowser,$ as inspectChromiumPdf,W as runBrowser};

SHA-256: 00263d1d6760ab7d4aae7a8676595d2d232dc2b310ab3e212babaed1dac945ed