{"record":{"id":"c9a030bb1c4086b0","repo":"jackwener/OpenCLI","slug":"label-resolved-c9a030","errorCode":null,"errorMessage":"${label}路径不是文件: ${resolved}","messagePattern":"(.+?)路径不是文件: (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/wechat-channels/publish.js","lineNumber":73,"sourceCode":"    return result.data;\n  }\n  return result;\n}\n\nasync function evalPage(page, script) {\n  return unwrapEvaluateResult(await page.evaluate(script));\n}\n\nfunction requireFilePath(filePath, label, allowedExts) {\n  const resolved = path.resolve(String(filePath));\n  let stat;\n  try {\n    stat = fs.statSync(resolved);\n  } catch {\n    throw new ArgumentError(`${label}文件不存在: ${resolved}`);\n  }\n  if (!stat.isFile()) {\n    throw new ArgumentError(`${label}路径不是文件: ${resolved}`);\n  }\n  const ext = path.extname(resolved).toLowerCase();\n  if (!allowedExts.has(ext)) {\n    throw new ArgumentError(`不支持的${label}格式: ${ext}（支持 ${Array.from(allowedExts).join('/')}）`);\n  }\n  return resolved;\n}\n\nfunction parseTimeoutSeconds(raw) {\n  const timeout = raw == null || raw === '' ? 600 : Number(raw);\n  if (!Number.isInteger(timeout) || timeout < 30) {\n    throw new ArgumentError('--timeout must be an integer >= 30 seconds');\n  }\n  return timeout;\n}\n\nfunction parseScheduleDate(raw) {\n  if (!raw) return null;","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/wechat-channels/publish.js#L55-L91","documentation":"requireFilePath validates the video path before upload. After fs.statSync succeeds, it checks stat.isFile() and throws ArgumentError when the resolved path exists but is not a regular file — typically a directory, symlink-to-directory, socket, or device node. This guards the uploader from being handed a non-file path that would fail later inside the browser automation.","triggerScenarios":"Calling videoPath (or requireFilePath directly) with a path that resolves to a directory (e.g. --video ./clips) or to a special file; passing a glob that a shell left unresolved; pointing at a mount point or device path.","commonSituations":"User passes the output directory instead of the rendered video file; automation passes a temp directory; a symlink resolves to a directory; copy-pasted path lost the filename portion.","solutions":["Pass the full path to the actual video file, not its containing directory.","Run ls -l <path> (or fs.statSync in Node) to confirm the path is a regular file.","If using a variable/script, ensure it does not drop the filename (e.g. path.resolve(dir) instead of path.resolve(dir, name))."],"exampleFix":"// before\nnode publish.js --video ./exports\n// after\nnode publish.js --video ./exports/final.mp4","handlingStrategy":"validation","validationCode":"const fs = require('fs'), path = require('path');\nconst resolved = path.resolve(videoPath);\nif (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {\n  throw new Error(`Not a file: ${resolved}`);\n}","typeGuard":"function isExistingFile(p) {\n  try { return fs.statSync(p).isFile(); } catch { return false; }\n}","tryCatchPattern":"try {\n  await publish({ videoPath });\n} catch (e) {\n  if (/路径不是文件/.test(e.message)) {\n    console.error(`Fix --video: ${videoPath} is a directory/special file`);\n  }\n  throw e;\n}","preventionTips":["Always pass the full path including filename, never a directory.","Validate with fs.statSync().isFile() in wrapper scripts before invoking the CLI.","Avoid glob patterns in --video unless your shell expands them."],"tags":["cli","argument-validation","filesystem"],"backgroundTag":"path-is-not-a-file","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}