{"record":{"id":"fef5ec6502ac09d2","repo":"jackwener/OpenCLI","slug":"novel-download-format-must-be-txt-or-md","errorCode":null,"errorMessage":"Novel download format must be txt or md","messagePattern":"Novel download format must be txt or md","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/pixiv/novel-download-utils.js","lineNumber":49,"sourceCode":"  const createdDate = dateOnly(body.createDate);\n  const wordCount = optionalDownloadCount(body.wordCount, 'word count');\n  const bookmarkCount = optionalDownloadCount(body.bookmarkCount, 'bookmark count');\n  return {\n    ...body,\n    id: novelId,\n    title,\n    userName: author,\n    userId,\n    content: body.content,\n    createdDate,\n    wordCount,\n    bookmarkCount,\n  };\n}\n\nexport function normalizeNovelFileFormat(value) {\n  if (value !== undefined && typeof value !== 'string') {\n    throw new ArgumentError('Novel download format must be txt or md');\n  }\n  const format = (value ?? 'txt').toLowerCase();\n  if (format !== 'txt' && format !== 'md') {\n    throw new ArgumentError(`Unsupported novel download format: ${format}. Supported formats: txt, md.`);\n  }\n  return format;\n}\n\nexport function normalizePixivOutputRoot(value, fallback) {\n  if (value !== undefined && typeof value !== 'string') {\n    throw new ArgumentError('output must be a directory path');\n  }\n  const raw = value ?? fallback;\n  if (!raw || raw.includes('\\0')) {\n    throw new ArgumentError('output must be a non-empty directory path');\n  }\n  const resolved = path.resolve(raw);\n  let ancestor = resolved;","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/pixiv/novel-download-utils.js#L31-L67","documentation":"normalizeNovelFileFormat rejects a --format argument that is defined but not a string, throwing this ArgumentError before any download starts. Only string values can be normalized to txt or md.","triggerScenarios":"Passing a non-string to the format option programmatically (number, boolean, object) via the API or a config loader that yields typed values.","commonSituations":"Config file parsed with a YAML/JSON parser producing a boolean/number for format (e.g. format: true or format: 1); calling the exported function directly with a wrong type.","solutions":["Quote the format value in config so parsers keep it a string: format: \"txt\"","Pass only 'txt' or 'md' as the format","Fix the config schema/parser that is coercing the value to a non-string"],"exampleFix":"// before (config.yaml)\nformat: true\n// after\nformat: \"md\"","handlingStrategy":"validation","validationCode":"const fmt = options.format;\nif (fmt !== undefined && typeof fmt !== 'string') {\n  throw new Error(`format must be a string ('txt' or 'md'), got ${typeof fmt}`);\n}","typeGuard":"const isFormatValue = (v) => v === undefined || typeof v === 'string';","tryCatchPattern":"try {\n  await novelDownload({ format });\n} catch (e) {\n  if (e.message === 'Novel download format must be txt or md') {\n    console.error('format must be a string; check your config parser types');\n  } else throw e;\n}","preventionTips":["Quote format values in YAML/JSON configs so they parse as strings","Never pass booleans/numbers as format","Sanitize config values with String() only when they are already string-like","Validate config schema before invoking the CLI"],"tags":["validation","cli","argument"],"backgroundTag":"invalid-argument-type","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}