{"record":{"id":"f4e20da07261a2d3","repo":"mjmlio/mjml","slug":"specified-filepath-does-not-exist-f4e20d","errorCode":null,"errorMessage":"Specified filePath does not exist","messagePattern":"Specified filePath does not exist","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/mjml-parser-xml/src/index.js","lineNumber":54,"sourceCode":"    actualPath = '.',\n    ignoreIncludes = true,\n    preprocessors = [],\n    includePath,\n  } = options\n\n  const endingTags = flow(\n    filter((component) => component.endingTag),\n    map((component) => component.getTagName()),\n  )({ ...components })\n\n  let cwd = process.cwd()\n\n  if (isNode && filePath) {\n    try {\n      const isDir = fs.lstatSync(filePath).isDirectory()\n      cwd = isDir ? filePath : path.dirname(filePath)\n    } catch (e) {\n      throw new Error('Specified filePath does not exist')\n    }\n  }\n\n  let mjml = null\n  let cur = null\n  let inInclude = !!includedIn.length\n  let inEndingTag = 0\n  const cssIncludes = []\n  const currentEndingTagIndexes = { startIndex: 0, endIndex: 0 }\n\n  const findTag = (tagName, tree) => find(tree.children, { tagName })\n  const lineIndexes = indexesForNewLine(xml)\n\n  const extraAllowedRoots = []\n  const addAllowedRoot = (p) => {\n    if (!p) return\n    try {\n      const resolved = fs.realpathSync(path.resolve(cwd, p))","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/mjmlio/mjml/blob/6c01d35af5da14108b86bbf052232e99de7ca755/packages/mjml-parser-xml/src/index.js#L36-L72","documentation":"MJMLParser, when given a filePath option in Node, lstats it to determine the base directory used to resolve <mj-include> paths. Any lstat failure (most commonly ENOENT) makes it throw 'Specified filePath does not exist'. This protects include resolution: without a valid base directory, relative includes could silently resolve to the wrong place.","triggerScenarios":"Calling MJMLParser (directly, or via mjmlJson/partialMjml/mjml2html) with { filePath: '/nonexistent/path.mjml' } or a path that was deleted/moved; passing a filePath option while parsing a string whose 'source file' never existed on disk.","commonSituations":"Parsing MJML from a string but passing a filePath of a temp file already cleaned up; build tools deriving filePath from a request URL instead of a real file; directory renames between dev and CI.","solutions":["Verify the filePath exists (fs.existsSync / lstat) before calling the parser and fix the path.","If parsing a string, omit the filePath option or point it at the real source file so includes resolve.","Use an absolute path to eliminate cwd ambiguity.","Ensure any temp files written for parsing are not deleted before MJMLParser runs."],"exampleFix":"// before\nMJMLParser(mjmlString, { filePath: '/tmp/old.mjml' }) // deleted\n// after\nMJMLParser(mjmlString) // no includes, drop filePath\n// or: { filePath: path.resolve('src/newsletter.mjml') }","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nfunction safeFilePathOption(p) {\n  if (!p) return {};\n  if (!fs.existsSync(p)) throw new Error(`filePath option does not exist: ${p}`);\n  return { filePath: p };\n}\nMJMLParser(xml, safeFilePathOption(filePath));","typeGuard":null,"tryCatchPattern":"try {\n  return MJMLParser(xml, { filePath })\n} catch (e) {\n  if (e.message === 'Specified filePath does not exist') {\n    console.error(`Bad filePath option: ${filePath}; cwd=${process.cwd()}`)\n  } else throw e\n}","preventionTips":["Only pass filePath when parsing real on-disk files.","Resolve include paths against absolute base directories.","Keep temp source files alive until parsing completes."],"tags":["filesystem","parser","missing-file","path"],"backgroundTag":"file-not-found","analyzedSha":"6c01d35af5da14108b86bbf052232e99de7ca755","analyzedAt":"2026-09-02T21:31:03.649Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}