{"record":{"id":"0ed72ea1b0cda0d7","repo":"DietrichGebert/ponytail","slug":"relpath-e-message","errorCode":null,"errorMessage":"${relPath}: ${e.message}","messagePattern":"\\$\\{relPath\\}: \\$\\{e\\.message\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/check-versions.js","lineNumber":38,"sourceCode":"// manifests here so a future ecosystem can't drift unnoticed.\nconst VERSION_FILES = [\n  '.claude-plugin/plugin.json',  // Claude Code plugin — what users install\n  '.codex-plugin/plugin.json',   // Codex plugin\n  '.devin-plugin/plugin.json',   // Devin CLI plugin\n  '.github/plugin/plugin.json',  // Copilot plugin\n  '.qoder-plugin/plugin.json',   // Qoder plugin\n  'gemini-extension.json',       // Gemini CLI extension\n  'package.json',                // pi-package / repo root\n  'ponytail-mcp/package.json',   // MCP server (private, internal-only)\n];\n\nfunction readVersion(relPath) {\n  try {\n    // Strip a UTF-8 BOM some Windows editors prepend (breaks JSON.parse).\n    const raw = fs.readFileSync(path.join(root, relPath), 'utf8').replace(/^\\uFEFF/, '');\n    return JSON.parse(raw).version;\n  } catch (e) {\n    throw new Error(`${relPath}: ${e.message}`);\n  }\n}\n\nlet failed = false;\nconst versions = VERSION_FILES.map((relPath) => {\n  const version = readVersion(relPath);\n  if (typeof version !== 'string' || !PINNED_SEMVER.test(version)) {\n    console.error(`${relPath}: version must be a pinned X.Y.Z semver, got ${JSON.stringify(version)}`);\n    failed = true;\n  }\n  return [relPath, version];\n});\n\n// Every file must declare the same version.\nconst distinct = [...new Set(versions.map(([, v]) => v))];\nif (distinct.length > 1) {\n  console.error('Version mismatch — every manifest must share one version:');\n  for (const [relPath, version] of versions) console.error(`  ${version}\\t${relPath}`);","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/DietrichGebert/ponytail/blob/2ed6c52c9d7e5e56942508591085fd45dea277d3/scripts/check-versions.js#L20-L56","documentation":"Thrown by readVersion() in scripts/check-versions.js. For each path in VERSION_FILES, the function reads the file (stripping a leading UTF-8 BOM), JSON.parse()s it, and returns .version. Any read or parse failure is caught and re-thrown as a wrapped error prefixed with the relPath so the failing manifest is identifiable. This is a fan-in wrapper: the original cause (ENOENT, SyntaxError, EACCES) is preserved in e.message and only the relPath context is added. Note a missing 'version' field does NOT throw here — it returns undefined and is flagged later by the PINNED_SEMVER check.","triggerScenarios":"Running node scripts/check-versions.js (typically via npm version / release / CI) when one of VERSION_FILES is missing, unreadable, or contains invalid JSON (merge-conflict markers, trailing comma, unterminated string, HTML error page saved as JSON). Adding a new manifest path to VERSION_FILES before the file exists. A botched merge leaves conflict markers inside package.json.","commonSituations":"A new manifest (e.g. gemini-extension.json) is added to VERSION_FILES but not yet committed/created. A package.json is corrupted by a merge conflict or a manual edit with a trailing comma. File permissions block reads in CI. A version bump script renames/moves a manifest and forgets to update VERSION_FILES.","solutions":["Read the relPath in the error message and open that exact file; verify it exists at the repo root.","Validate the JSON: node -e \"JSON.parse(require('fs').readFileSync('<relPath>','utf8'))\" — the resulting SyntaxError pinpoints the bad token.","Remove any merge-conflict markers (<<<<<< / ======= / >>>>>>), trailing commas, or BOM/HTML, and re-save as clean UTF-8 JSON.","If the file genuinely should not exist yet, either create it with a {\"version\":\"X.Y.Z\"} body or remove its entry from VERSION_FILES until it ships.","Re-run node scripts/check-versions.js; expect the PINNED_SEMVER / mismatch checks to take over once parsing succeeds."],"exampleFix":"// before — ponytail-mcp/package.json (merge conflict left in file)\n{\n  \"name\": \"ponytail-mcp\",\n<<<<<<< HEAD\n  \"version\": \"1.2.0\"\n=======\n  \"version\": \"1.2.1\"\n>>>>>>> main\n}\n\n// after — clean JSON, single pinned version\n{\n  \"name\": \"ponytail-mcp\",\n  \"version\": \"1.2.1\"\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: ensure every VERSION_FILES entry exists and parses before readVersion runs\nconst fs = require('fs'), path = require('path');\nfor (const rel of VERSION_FILES) {\n  const p = path.join(root, rel);\n  if (!fs.existsSync(p)) { console.error(`missing: ${rel}`); continue; }\n  try { JSON.parse(fs.readFileSync(p, 'utf8').replace(/^\\uFEFF/, '')); }\n  catch (e) { console.error(`${rel}: ${e.message}`); }\n}","typeGuard":"function isPinnedSemver(v) { return typeof v === 'string' && /^\\d+\\.\\d+\\.\\d+$/.test(v); }","tryCatchPattern":"// Keep the existing relPath-prefixing wrapper; just preserve the cause chain\nfunction readVersion(relPath) {\n  try {\n    const raw = fs.readFileSync(path.join(root, relPath), 'utf8').replace(/^\\uFEFF/, '');\n    const v = JSON.parse(raw).version;\n    if (!isPinnedSemver(v)) throw new Error(`version must be pinned X.Y.Z, got ${JSON.stringify(v)}`);\n    return v;\n  } catch (e) {\n    const wrapped = new Error(`${relPath}: ${e.message}`);\n    wrapped.cause = e;\n    throw wrapped;\n  }\n}","preventionTips":["Run check-versions in pre-commit and CI so a corrupted manifest blocks the commit, not the release.","When adding a manifest to VERSION_FILES, commit the file in the same change.","Never hand-edit package.json during a merge — resolve conflicts in an editor that validates JSON on save.","Treat a BOM-aware parse (strip /^\\uFEFF/) as the single source of truth; do not re-add BOM stripping ad hoc."],"tags":["config","json","versioning","build","release"],"backgroundTag":null,"analyzedSha":"2ed6c52c9d7e5e56942508591085fd45dea277d3","analyzedAt":"2026-08-12T23:02:21.847Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}