santifer/career-ops · error

Unknown template format

Error message

Unknown template format: ${format}

What it means

stripEmptySections removes optional sections with no payload entries, but it looks up marker patterns via PATTERNS[format] first. If the format string isn't a registered key in PATTERNS (only formats with defined section-marker patterns qualify), it throws instead of guessing, since without patterns it cannot know what to strip.

Solutions

  1. Pass a registered format ('html' or 'tex') as the third argument.
  2. Check the PATTERNS map in cv-sections-core.mjs for valid keys.
  3. Fix case sensitivity ('HTML' → 'html').
  4. If adding a new format, register a full PATTERNS entry for it first.

Example fix

// before
stripEmptySections(template, payload, 'markdown');

// after
stripEmptySections(template, payload, 'html');
Defensive patterns

Strategy: validation

Validate before calling

import { PATTERNS } from './cv-sections-core.mjs';
function canStrip(format) {
  return Object.prototype.hasOwnProperty.call(PATTERNS, format);
}
// if (!canStrip(format)) throw new Error(`stripEmptySections: unregistered format '${format}'`);

Type guard

const hasPatterns = (fmt) => fmt != null && typeof PATTERNS[fmt] === 'object';

Try / catch

try {
  out = stripEmptySections(tpl, payload, format);
} catch (e) {
  if (e.message.startsWith('Unknown template format:')) {
    console.error(`Format '${format}' has no section patterns; returning template unstripped`);
    out = tpl;
  } else throw e;
}

Prevention

When it happens

Trigger: Calling stripEmptySections(template, payload, 'md') or any format other than the registered keys (e.g. 'html'/'tex' as wired into the builders); passing undefined format from an unvalidated option; a typo like 'HTML' if PATTERNS keys are lowercase.

Common situations: Adding a new output format without registering its PATTERNS entry; calling the shared core directly from custom tooling with an ad-hoc format name; case-mismatched format strings from CLI parsing.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/aba5e1cebe2553e7. Report an issue: GitHub.

Appendix: source

Thrown at cv-sections-core.mjs:144

    projects: new RegExp(String.raw`%{4,}\s+PROJECTS\s+%{4,}[\s\S]*?` + TEX_BOUNDARY),
    education: new RegExp(String.raw`%{4,}\s+Education\s+%{4,}[\s\S]*?` + TEX_BOUNDARY),
    awards: new RegExp(String.raw`%{4,}\s+AWARDS\s+%{4,}[\s\S]*?` + TEX_BOUNDARY),
    skills: new RegExp(String.raw`%{4,}\s+Technical Skills\s+%{4,}[\s\S]*?` + TEX_END_SENTINEL, 'm'),
  },
};

export const OPTIONAL_SECTIONS = ['competencies', 'experience', 'projects', 'education', 'certifications', 'awards', 'interests', 'skills'];

export function isEmptySection(payload, section) {
  const entries = payload?.[section];
  return !Array.isArray(entries) || entries.length === 0;
}

// Remove every optional section that has no entries in `payload`. Returns the
// template unchanged when both are populated.
export function stripEmptySections(template, payload, format) {
  const patterns = PATTERNS[format];
  if (!patterns) throw new Error(`Unknown template format: ${format}`);

  let out = template;
  for (const section of OPTIONAL_SECTIONS) {
    const pattern = patterns[section];
    if (!pattern) continue; // this format's template has no marker for this section
    if (isEmptySection(payload, section)) {
      // A non-matching pattern is a no-op here by design — see the Skills
      // sentinel note above: no sentinel means no strip, never a fallback to
      // a looser boundary.
      out = out.replace(pattern, '');
    }
  }
  return out;
}

View on GitHub (pinned to aac998c7ed)