{"record":{"id":"36cd3c98d1be3024","repo":"koala73/worldmonitor","slug":"pro-html-path-must-carry-exactly-one-datemodified-node-found","errorCode":null,"errorMessage":"${PRO_HTML_PATH} must carry exactly one dateModified node (found ${dates.length})","messagePattern":"(.+?) must carry exactly one dateModified node \\(found (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/build-welcome-teasers.mjs","lineNumber":264,"sourceCode":"  const html = readFileSync(htmlPath, 'utf8');\n  const lastmodMatches = html.match(/<meta name=\"lastmod\" content=\"\\d{4}-\\d{2}-\\d{2}\" \\/>/g) || [];\n  const dateModifiedMatches = html.match(/\"dateModified\": \"\\d{4}-\\d{2}-\\d{2}\"/g) || [];\n  if (lastmodMatches.length !== 1 || dateModifiedMatches.length !== 1) {\n    throw new Error(\n      `${WELCOME_HTML_PATH} must carry exactly one lastmod meta and one dateModified node `\n      + `(found ${lastmodMatches.length} / ${dateModifiedMatches.length}) — refusing to guess which one tracks the strip snapshot`,\n    );\n  }\n  return html\n    .replace(/<meta name=\"lastmod\" content=\"\\d{4}-\\d{2}-\\d{2}\" \\/>/, `<meta name=\"lastmod\" content=\"${capturedAt}\" />`)\n    .replace(/\"dateModified\": \"\\d{4}-\\d{2}-\\d{2}\"/, `\"dateModified\": \"${capturedAt}\"`);\n}\n\nexport function renderProHtml({ rootDir = REPO_ROOT, capturedAt } = {}) {\n  const html = readFileSync(join(rootDir, PRO_HTML_PATH), 'utf8');\n  const dates = html.match(/\"dateModified\": \"\\d{4}-\\d{2}-\\d{2}\"/g) || [];\n  if (dates.length !== 1) {\n    throw new Error(`${PRO_HTML_PATH} must carry exactly one dateModified node (found ${dates.length})`);\n  }\n  return html.replace(/\"dateModified\": \"\\d{4}-\\d{2}-\\d{2}\"/, `\"dateModified\": \"${capturedAt}\"`);\n}\n\nconst isMain = process.argv[1] && resolve(process.argv[1]) === __filename;\nif (isMain) {\n  const check = process.argv.includes('--check');\n  const snapshotPath = resolveLatestLivePulseSnapshotPath(REPO_ROOT);\n  const snapshot = JSON.parse(readFileSync(join(REPO_ROOT, snapshotPath), 'utf8'));\n  const teasers = buildWelcomeTeasers(snapshot, snapshotPath);\n  const expectedTeasers = `${JSON.stringify({ _comment: comment(snapshotPath, snapshot.capturedAt), ...teasers }, null, 2)}\\n`;\n  const expectedHtml = renderWelcomeHtml({ rootDir: REPO_ROOT, capturedAt: teasers.capturedAt });\n  const expectedProHtml = renderProHtml({ rootDir: REPO_ROOT, capturedAt: teasers.capturedAt });\n  const outPath = join(REPO_ROOT, TEASERS_OUTPUT_PATH);\n  const htmlPath = join(REPO_ROOT, WELCOME_HTML_PATH);\n  const proHtmlPath = join(REPO_ROOT, PRO_HTML_PATH);\n  if (check) {\n    const stale = [];","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/build-welcome-teasers.mjs#L246-L282","documentation":"scripts/build-welcome-teasers.mjs updates the JSON-LD `dateModified` field in the PRO welcome HTML so its schema.org metadata matches the capture date. The build asserts the file contains exactly one `\"dateModified\": \"YYYY-MM-DD\"` node; if the regex finds zero or multiple occurrences, renderProHtml throws rather than silently rewriting the wrong (or no) node. This guards against a stale or duplicated structured-data block corrupting SEO metadata.","triggerScenarios":"Calling renderProHtml (or running the script as main) when public PRO_HTML_PATH file contains 0 dateModified nodes (field renamed, quoting style changed to single quotes or no space after colon) or 2+ nodes (duplicate JSON-LD blocks added, template concatenation duplicated the field).","commonSituations":"A developer edits the welcome HTML template and reformats the JSON-LD (e.g. drops the space after the colon, so the strict regex no longer matches → 0 found); a templating step injects the JSON-LD block twice; merging branches duplicates the metadata node.","solutions":["Open the PRO HTML file and grep for `\"dateModified\":` — ensure exactly one occurrence in the exact form `\"dateModified\": \"YYYY-MM-DD\"`","If zero matches, restore/fix the JSON-LD dateModified node to the exact format the regex expects","If multiple matches, remove the duplicate JSON-LD block or extra dateModified fields, keeping one","If the file intentionally uses a different date format, update both the match/replace regex in scripts/build-welcome-teasers.mjs and the file consistently"],"exampleFix":"// before (duplicated/stale JSON-LD in pro html)\n\"dateModified\": \"2024-01-01\"\n...\n\"dateModified\": \"2023-11-05\"\n// after\n\"dateModified\": \"2024-01-01\"","handlingStrategy":"validation","validationCode":"const dates = html.match(/\"dateModified\": \"\\d{4}-\\d{2}-\\d{2}\"/g) || [];\nif (dates.length !== 1) throw new Error(`expected exactly 1 dateModified node, found ${dates.length}`);","typeGuard":"const hasSingleDateModified = (html) => (html.match(/\"dateModified\": \"\\d{4}-\\d{2}-\\d{2}\"/g) || []).length === 1;","tryCatchPattern":"try {\n  const html = renderProHtml({ capturedAt });\n} catch (e) {\n  if (e.message.includes('exactly one dateModified')) fixJsonLdInTemplate();\n  else throw e;\n}","preventionTips":["Keep the JSON-LD dateModified in the exact `\"dateModified\": \"YYYY-MM-DD\"` format","Never duplicate the JSON-LD structured-data block when editing the template","Add a lint/grep CI check asserting exactly one dateModified occurrence","Run the teaser build after any welcome-HTML template change"],"tags":["build","schema-validation","seo","regex"],"backgroundTag":"schema-validation-failed","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}