{"id":"9a29aa74b5c37612","repo":"vitest-dev/vitest","slug":"the-projects-glob-matched-a-file-relative-paren","errorCode":null,"errorMessage":"The projects glob matched a file \"${relative(parentConfig.root, path)}\", but it should also either start with \"vitest.config\"/\"vite.config\" or match the pattern \"(vitest|vite).*.config.*\".","messagePattern":"The projects glob matched a file \"(.+?)\", but it should also either start with \"vitest\\.config\"/\"vite\\.config\" or match the pattern \"\\(vitest\\|vite\\)\\.\\*\\.config\\.\\*\"\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/node/projects/resolveProjects.ts","lineNumber":1071,"sourceCode":"\n    const projectsFs = await glob(projectsGlobMatches, globOptions)\n\n    projectsFs.forEach((path) => {\n      // directories are allowed with a glob like `packages/*`\n      // in this case every directory is treated as a project\n      if (path.endsWith('/')) {\n        const configFile = resolveDirectoryConfig(path)\n        if (configFile) {\n          projectsConfigFiles.push(configFile)\n        }\n        else {\n          nonConfigProjectDirectories.push(path)\n        }\n      }\n      else {\n        const name = basename(path)\n        if (!CONFIG_REGEXP.test(name)) {\n          throw new Error(\n            `The projects glob matched a file \"${relative(parentConfig.root, path)}\", `\n            + `but it should also either start with \"vitest.config\"/\"vite.config\" `\n            + `or match the pattern \"(vitest|vite).*.config.*\".`,\n          )\n        }\n        projectsConfigFiles.push(path)\n      }\n    })\n  }\n\n  const projectConfigFiles = Array.from(new Set(projectsConfigFiles))\n\n  return {\n    projectConfigs: projectsOptions,\n    nonConfigDirectories: nonConfigProjectDirectories,\n    configFiles: projectConfigFiles,\n  }\n}","sourceCodeStart":1053,"sourceCodeEnd":1089,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/node/projects/resolveProjects.ts#L1053-L1089","documentation":"Thrown during project resolution when the `projects` glob in your vitest config matches a filesystem entry that is a plain file but does not look like a Vite/Vitest config file. Vitest validates the file's basename against the regex `/^vite(?:st)?(?:\\.[\\w-]+)?\\.config\\./` (defined at resolveProjects.ts:40); only files matching that pattern (e.g. `vitest.config.ts`, `vite.workspace.config.mjs`) are accepted as project configs. Any other matched file aborts resolution because Vitest cannot infer how to load it as a project.","triggerScenarios":"Setting `projects: ['packages/*']` where one of the matched entries is a file (not a directory) whose name fails CONFIG_REGEXP, e.g. a `packages/README.md` or a stray `packages/index.js`. Also triggered by a glob that accidentally includes dotfiles or build artifacts like `packages/vitest.rollup.config.js` only if the segment after `vite`/`vitest` contains chars outside `[\\w-]`. Directories ending with `/` are handled separately (resolveDirectoryConfig), so only bare-file matches hit this branch.","commonSituations":"Pointing `projects` at a flat layout where packages are files rather than folders; renaming a config to something non-conventional (e.g. `vitest.conf.ts`); a glob like `**/vitest.*` that also catches non-config files; monorepo migrations that leave behind a file matching the glob but not the naming convention.","solutions":["Change the `projects` glob so it only matches directories (e.g. `packages/*` matched against folders) or config files specifically.","Rename the matched file to follow the `vitest.config.*` / `vite.config.*` / `vitest.<scope>.config.*` convention.","Add more specific ignore patterns or narrow the glob to `**/vitest.config.*` to avoid catching unrelated files.","Delete or relocate the stray non-config file that the glob is matching."],"exampleFix":"// before (vitest.config.ts)\nexport default defineConfig({\n  projects: ['packages/*'] // matches packages/shared.js as a file\n})\n// after\nexport default defineConfig({\n  projects: ['packages/*/vitest.config.ts']\n})","handlingStrategy":"validation","validationCode":"import { glob } from 'tinyglobby'\nimport { basename } from 'node:path'\nconst CONFIG_REGEXP = /^vite(?:st)?(?:\\.[\\w-]+)?\\.config\\./\nconst matches = await glob(['packages/*'], { cwd: root, absolute: true, onlyFiles: false, dot: true })\nconst bad = matches.filter(p => !p.endsWith('/') && !CONFIG_REGEXP.test(basename(p)))\nif (bad.length) throw new Error(`Non-config files matched by projects glob: ${bad.join(', ')}`)","typeGuard":"import { basename } from 'node:path'\nconst CONFIG_REGEXP = /^vite(?:st)?(?:\\.[\\w-]+)?\\.config\\./\nconst isConfigFile = (p: string): boolean => CONFIG_REGEXP.test(basename(p))","tryCatchPattern":null,"preventionTips":["Prefer directory-style globs (`packages/*`) so non-config files are skipped via directory config resolution.","When globbing files directly, restrict to `**/vitest.config.*` / `**/vite.config.*`.","Run a dry glob of your `projects` value before committing the config."],"tags":["config","projects","glob","monorepo"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}