{"id":"2d50f13e90d7f6ee","repo":"vitest-dev/vitest","slug":"invalid-export-in-globalsetup-file-file-exp","errorCode":null,"errorMessage":"invalid export in globalSetup file ${file}: ${exp} must be a function","messagePattern":"invalid export in globalSetup file (.+?): (.+?) must be a function","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/node/globalSetup.ts","lineNumber":28,"sourceCode":"\nexport async function loadGlobalSetupFiles(\n  runner: ModuleRunner,\n  globalSetup: string | string[],\n): Promise<GlobalSetupFile[]> {\n  const globalSetupFiles = toArray(globalSetup)\n  return Promise.all(\n    globalSetupFiles.map(file => loadGlobalSetupFile(file, runner)),\n  )\n}\n\nasync function loadGlobalSetupFile(\n  file: string,\n  runner: ModuleRunner,\n): Promise<GlobalSetupFile> {\n  const m = await runner.import(file)\n  for (const exp of ['default', 'setup', 'teardown']) {\n    if (m[exp] != null && typeof m[exp] !== 'function') {\n      throw new Error(\n        `invalid export in globalSetup file ${file}: ${exp} must be a function`,\n      )\n    }\n  }\n  if (m.default) {\n    return {\n      file,\n      setup: m.default,\n    }\n  }\n  else if (m.setup || m.teardown) {\n    return {\n      file,\n      setup: m.setup,\n      teardown: m.teardown,\n    }\n  }\n  else {","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/node/globalSetup.ts#L10-L46","documentation":"loadGlobalSetupFile (globalSetup.ts:28) imports the module and, for each of default/setup/teardown, requires that a defined export be a function. If any of those names is exported as something else (object, string, array), vitest rejects it rather than calling a non-function.","triggerScenarios":"A globalSetup file that exports default, setup, or teardown as a non-function value — e.g. a config object, an array of hooks, or a constant.","commonSituations":"Accidentally exporting a config/helper object under one of the reserved names; refactoring a setup file and leaving a stale export.","solutions":["Make each of default/setup/teardown a function (or remove it).","Rename unrelated exports so they don't collide with the reserved names.","Use the default-export form: export default function setup(provide) { ... }."],"exampleFix":"// before\nexport const setup = { hooks: [...] }\n\n// after\nexport function setup(provide) { /* ... */ }","handlingStrategy":"type-guard","validationCode":"for (const k of ['default', 'setup', 'teardown']) {\n  if (mod[k] != null && typeof mod[k] !== 'function') {\n    throw new TypeError(`globalSetup export '${k}' must be a function`)\n  }\n}","typeGuard":"function isFunctionExport(mod: any, key: string): boolean {\n  return mod[key] == null || typeof mod[key] === 'function'\n}","tryCatchPattern":null,"preventionTips":["Reserve default/setup/teardown names for functions only.","Rename helper exports to avoid collisions.","Lint globalSetup files for the reserved export shapes."],"tags":["globalsetup","config","validation"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}