Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | /** * Handles the core rendering logic for a single spec. * Processes markdown files, renders HTML, applies fixes, and writes output. * It takes shared variables and config as parameters to maintain modularity. */ const fs = require('fs-extra'); const path = require('node:path'); const { fetchExternalSpecs, validateReferences, mergeXrefTermsIntoAllXTrefs } = require('../references/external-references-service.js'); const { processEscapedTags, restoreEscapedTags } = require('../preprocessing/escape-placeholder-utils.js'); const { sortDefinitionTermsInHtml, fixDefinitionListStructure } = require('../postprocessing/definition-list-postprocessor.js'); const { getGithubRepoInfo, getCurrentBranch } = require('../../utils/git-info.js'); const { templateTags } = require('../../utils/regex-patterns.js'); const { createScriptElementWithXTrefDataForEmbeddingInHtml, applyReplacers } = require('./render-utils.js'); const { warnOnHeadingHierarchyViolations } = require('./heading-hierarchy-validator.js'); const { copyStaticRoot } = require('./copy-static-root.js'); async function render(spec, assets, sharedVars, config, template, assetsGlobal, Logger, md, externalSpecsList) { let { externalReferences } = sharedVars; let renderedHtml; try { globalThis.noticeTitles = {}; globalThis.specGroups = {}; Logger.info('Rendering: ' + spec.title); function interpolate(template, variables) { return template.replace(templateTags.variableInterpolation, (match, p1) => variables[p1.trim()]); } // Add current date in 'DD Month YYYY' format for template injection const date = new Date(); const day = String(date.getDate()).padStart(2, '0'); const month = date.toLocaleString('en-US', { month: 'long' }); const year = date.getFullYear(); const currentDate = `${day} ${month} ${year}`; // Add universal timestamp in ISO 8601 format for template injection const universalTimestamp = date.toISOString(); // Get the branch name from which the index.html was generated const buildBranch = getCurrentBranch(); // Read spec-up-t version from its own package.json for build info display const specUpTVersion = require('../../../package.json').version; // Capture Node.js runtime version for build info display const nodeVersion = process.version; // Read all markdown files into an array const docs = await Promise.all( (spec.markdown_paths || ['spec.md']).map(_path => fs.readFile(spec.spec_directory + _path, 'utf8') ) ); const features = (({ source, logo }) => ({ source, logo }))(spec); if (spec.external_specs && !externalReferences) { // Fetch xref terms and merge them into allXTrefs instead of creating DOM HTML const xrefTerms = await fetchExternalSpecs(spec); // Define paths for the xtrefs data files const outputPathJSON = path.join('.cache', 'xtrefs-data.json'); const outputPathJS = path.join('.cache', 'xtrefs-data.js'); // Merge xref terms into the unified allXTrefs structure await mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPathJS); // Set flag to indicate external references have been processed externalReferences = true; // Changed from HTML array to boolean flag } // Find the index of the terms-and-definitions-intro.md file const termsIndex = (spec.markdown_paths || ['spec.md']).indexOf('terms-and-definitions-intro.md'); if (termsIndex !== -1) { // Marker used as the starting point of the terminology index. The newlines are essential for the correct rendering of the markdown. docs[termsIndex] += '\n\n<div id="terminology-section-start"></div>\n\n'; } // Set up file tracking for definitions before rendering for (let i = 0; i < docs.length; i++) { globalThis.currentFile = spec.markdown_paths[i] || 'unknown'; docs[i] = `<!-- file: ${globalThis.currentFile} -->\n${docs[i]}`; } // Concatenate all file contents into one string, separated by newlines let doc = docs.join("\n"); // Handles backslash escape mechanism for substitution tags // Phase 1: Pre-processing - Handle escaped tags doc = processEscapedTags(doc); // Handles backslash escape mechanism for substitution tags // Phase 2: Tag Processing - Apply normal substitution logic doc = applyReplacers(doc); md[spec.katex ? "enable" : "disable"](['math_block', 'math_inline']); // Suppress markdown-it-attrs table errors during rendering // // Background: markdown-it-attrs can throw "Cannot read properties of null (reading 'colsnum')" // errors when processing malformed tables. These errors are: // 1. Non-blocking: The rendering continues and index.html is generated successfully // 2. Caught proactively: The markdown-tables healthcheck detects these issues // 3. Noisy: They clutter the console output without providing actionable information // // Solution: Intercept console.error during md.render() to suppress only these specific // table-related errors while allowing all other errors to pass through normally. const originalConsoleError = console.error; let suppressNext = false; console.error = (...args) => { const message = args[0]?.toString() || ''; // Check if this is the first console.error call with the error message if (message.includes('markdown-it-attrs: Error in pattern') && message.includes('tables') && message.includes('colsnum')) { // Suppress this error and the next stack trace suppressNext = true; return; } // Check if this is the stack trace following the suppressed error if (suppressNext) { suppressNext = false; // Also suppress if this looks like a stack trace for colsnum error if (message.includes('colsnum') || message.includes('at Object.transform')) { return; } } // Let all other errors through originalConsoleError.apply(console, args); }; try { // `render` is the rendered HTML renderedHtml = md.render(doc); } finally { // Always restore console.error console.error = originalConsoleError; } // Apply the fix for broken definition list structures renderedHtml = fixDefinitionListStructure(renderedHtml); // Sort definition terms case-insensitively before final rendering renderedHtml = sortDefinitionTermsInHtml(renderedHtml); // Handles backslash escape mechanism for substitution tags // Phase 3: Post-processing - Restore escaped sequences as literals renderedHtml = restoreEscapedTags(renderedHtml); // Warn about heading hierarchy violations (W3C accessibility) warnOnHeadingHierarchyViolations(renderedHtml, Logger); const templateInterpolated = interpolate(template, { title: spec.title, description: spec.description, author: spec.author, toc: globalThis.toc, render: renderedHtml, assetsHead: assets.head, assetsBody: assets.body, assetsSvg: assets.svg, xtrefsData: createScriptElementWithXTrefDataForEmbeddingInHtml(), specLogo: spec.logo, specFavicon: spec.favicon, specLogoLink: spec.logo_link, spec: JSON.stringify(spec), externalSpecsList: externalSpecsList, currentDate: currentDate, universalTimestamp: universalTimestamp, buildBranch: buildBranch, specUpTVersion: specUpTVersion, nodeVersion: nodeVersion, githubRepoInfo: getGithubRepoInfo(spec) }); const outputPath = path.join(spec.destination, 'index.html'); Logger.info('Attempting to write to:', outputPath); // Use promisified version instead of callback await fs.promises.writeFile(outputPath, templateInterpolated, 'utf8'); Logger.success(`Successfully wrote ${outputPath}`); // Copy any deployment root files (CNAME, robots.txt, …) from spec/static-root/ copyStaticRoot(spec.spec_directory, spec.destination); validateReferences(globalThis.references, globalThis.definitions, renderedHtml); globalThis.references = []; globalThis.definitions = []; } catch (e) { Logger.error("Render error: " + e.message); throw e; } // Update sharedVars sharedVars.externalReferences = externalReferences; } module.exports = { render }; |