koala73/worldmonitor · error · Error

${PRO_HTML_PATH} must carry exactly one dateModified node (f

Error message

${PRO_HTML_PATH} must carry exactly one dateModified node (found ${dates.length})

What it means

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.

Solutions

  1. Open the PRO HTML file and grep for `"dateModified":` — ensure exactly one occurrence in the exact form `"dateModified": "YYYY-MM-DD"`
  2. If zero matches, restore/fix the JSON-LD dateModified node to the exact format the regex expects
  3. If multiple matches, remove the duplicate JSON-LD block or extra dateModified fields, keeping one
  4. 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

Example fix

// before (duplicated/stale JSON-LD in pro html)
"dateModified": "2024-01-01"
...
"dateModified": "2023-11-05"
// after
"dateModified": "2024-01-01"
Defensive patterns

Strategy: validation

Validate before calling

const dates = html.match(/"dateModified": "\d{4}-\d{2}-\d{2}"/g) || [];
if (dates.length !== 1) throw new Error(`expected exactly 1 dateModified node, found ${dates.length}`);

Type guard

const hasSingleDateModified = (html) => (html.match(/"dateModified": "\d{4}-\d{2}-\d{2}"/g) || []).length === 1;

Try / catch

try {
  const html = renderProHtml({ capturedAt });
} catch (e) {
  if (e.message.includes('exactly one dateModified')) fixJsonLdInTemplate();
  else throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/36cd3c98d1be3024. Report an issue: GitHub.

Appendix: source

Thrown at scripts/build-welcome-teasers.mjs:264

  const html = readFileSync(htmlPath, 'utf8');
  const lastmodMatches = html.match(/<meta name="lastmod" content="\d{4}-\d{2}-\d{2}" \/>/g) || [];
  const dateModifiedMatches = html.match(/"dateModified": "\d{4}-\d{2}-\d{2}"/g) || [];
  if (lastmodMatches.length !== 1 || dateModifiedMatches.length !== 1) {
    throw new Error(
      `${WELCOME_HTML_PATH} must carry exactly one lastmod meta and one dateModified node `
      + `(found ${lastmodMatches.length} / ${dateModifiedMatches.length}) — refusing to guess which one tracks the strip snapshot`,
    );
  }
  return html
    .replace(/<meta name="lastmod" content="\d{4}-\d{2}-\d{2}" \/>/, `<meta name="lastmod" content="${capturedAt}" />`)
    .replace(/"dateModified": "\d{4}-\d{2}-\d{2}"/, `"dateModified": "${capturedAt}"`);
}

export function renderProHtml({ rootDir = REPO_ROOT, capturedAt } = {}) {
  const html = readFileSync(join(rootDir, PRO_HTML_PATH), 'utf8');
  const dates = html.match(/"dateModified": "\d{4}-\d{2}-\d{2}"/g) || [];
  if (dates.length !== 1) {
    throw new Error(`${PRO_HTML_PATH} must carry exactly one dateModified node (found ${dates.length})`);
  }
  return html.replace(/"dateModified": "\d{4}-\d{2}-\d{2}"/, `"dateModified": "${capturedAt}"`);
}

const isMain = process.argv[1] && resolve(process.argv[1]) === __filename;
if (isMain) {
  const check = process.argv.includes('--check');
  const snapshotPath = resolveLatestLivePulseSnapshotPath(REPO_ROOT);
  const snapshot = JSON.parse(readFileSync(join(REPO_ROOT, snapshotPath), 'utf8'));
  const teasers = buildWelcomeTeasers(snapshot, snapshotPath);
  const expectedTeasers = `${JSON.stringify({ _comment: comment(snapshotPath, snapshot.capturedAt), ...teasers }, null, 2)}\n`;
  const expectedHtml = renderWelcomeHtml({ rootDir: REPO_ROOT, capturedAt: teasers.capturedAt });
  const expectedProHtml = renderProHtml({ rootDir: REPO_ROOT, capturedAt: teasers.capturedAt });
  const outPath = join(REPO_ROOT, TEASERS_OUTPUT_PATH);
  const htmlPath = join(REPO_ROOT, WELCOME_HTML_PATH);
  const proHtmlPath = join(REPO_ROOT, PRO_HTML_PATH);
  if (check) {
    const stale = [];

View on GitHub (pinned to 7d06c8633d)