{"record":{"id":"b969825b5405d268","repo":"hexojs/hexo","slug":"fn-must-be-a-function-b96982","errorCode":null,"errorMessage":"fn must be a function","messagePattern":"fn must be a function","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/extend/console.ts","lineNumber":90,"sourceCode":"  register(name: string, desc: string, fn: AnyFn): void\n  register(name: string, options: Option, fn: AnyFn): void\n  register(name: string, desc: string, options: Option, fn: AnyFn): void\n  register(name: string, desc: string | Option | AnyFn, options?: Option | AnyFn, fn?: AnyFn): void {\n    if (!name) throw new TypeError('name is required');\n\n    if (!fn) {\n      if (options) {\n        if (typeof options === 'function') {\n          fn = options;\n\n          if (typeof desc === 'object') { // name, options, fn\n            options = desc;\n            desc = '';\n          } else { // name, desc, fn\n            options = {};\n          }\n        } else {\n          throw new TypeError('fn must be a function');\n        }\n      } else {\n        // name, fn\n        if (typeof desc === 'function') {\n          fn = desc;\n          options = {};\n          desc = '';\n        } else {\n          throw new TypeError('fn must be a function');\n        }\n      }\n    }\n\n    if (fn.length > 1) {\n      fn = Promise.promisify(fn);\n    } else {\n      fn = Promise.method(fn);\n    }","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/hexojs/hexo/blob/059cb17494d0632a053c24077f5bcb6ae92acc51/lib/extend/console.ts#L72-L108","documentation":"Thrown by Console.register in the branch where three or four arguments were given, 'options' is truthy, but neither 'options' nor a later argument is a function. Hexo resolves the overloaded signatures (name,desc,fn | name,options,fn | name,desc,options,fn); when the expected fn slot is occupied by a non-function (e.g. an options object) and no real fn follows, the callback cannot be located.","triggerScenarios":"Calling register('name', 'desc', {}) (an options object but no fn), or register('name', { option: 1 }) where the second slot is an object and no function follows.","commonSituations":"A plugin author intended the (name, desc, fn) form but passed an options object as the third argument and omitted the function, or swapped the options and fn order.","solutions":["Ensure the function is the LAST argument in the (name, desc, options, fn) overload.","If you only need name+options+fn, put options third and fn fourth.","Double-check the overload you intend against the signature order."],"exampleFix":"// before\nhexo.extend.console.register('cmd', 'desc', { alias: 'c' });\n// after\nhexo.extend.console.register('cmd', 'desc', { alias: 'c' }, (args) => { /* ... */ });","handlingStrategy":"type-guard","validationCode":"const args = [name, desc, options, fn].filter(a => a !== undefined);\nif (typeof args[args.length - 1] !== 'function') throw new Error('last argument must be the handler');\nhexo.extend.console.register(name, desc, options, fn);","typeGuard":"const isFn = (x: unknown): x is (...a: any[]) => any => typeof x === 'function';","tryCatchPattern":null,"preventionTips":["Always place the handler as the final argument in overloaded register calls.","Write small wrapper helpers per overload to avoid positional mistakes.","Cover each overload signature with a registration test."],"tags":["console","cli","validation","overloads","plugin"],"backgroundTag":null,"analyzedSha":"059cb17494d0632a053c24077f5bcb6ae92acc51","analyzedAt":"2026-08-12T20:52:05.731Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}