{"record":{"id":"2d5aab1f44e1f4c1","repo":"composer/composer","slug":"package-s-must-have-a-source-or-dist-specified","errorCode":null,"errorMessage":"Package %s must have a source or dist specified","messagePattern":"Package (.+?) must have a source or dist specified","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Composer/Downloader/DownloadManager.php","lineNumber":452,"sourceCode":"     * @return string[]\n     * @phpstan-return array<'dist'|'source'>&non-empty-array\n     */\n    private function getAvailableSources(PackageInterface $package, ?PackageInterface $prevPackage = null): array\n    {\n        $sourceType = $package->getSourceType();\n        $distType = $package->getDistType();\n\n        // add source before dist by default\n        $sources = [];\n        if ($sourceType) {\n            $sources[] = 'source';\n        }\n        if ($distType) {\n            $sources[] = 'dist';\n        }\n\n        if (empty($sources)) {\n            throw new \\InvalidArgumentException('Package '.$package.' must have a source or dist specified');\n        }\n\n        if (\n            $prevPackage\n            // if we are updating, we want to keep the same source as the previously installed package (if available in the new one)\n            && in_array($prevPackage->getInstallationSource(), $sources, true)\n            // unless the previous package was stable dist (by default) and the new package is dev, then we allow the new default to take over\n            && !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev())\n        ) {\n            $prevSource = $prevPackage->getInstallationSource();\n            usort($sources, static function ($a, $b) use ($prevSource): int {\n                return $a === $prevSource ? -1 : 1;\n            });\n\n            return $sources;\n        }\n\n        // reverse sources in case dist is the preferred source for this package","sourceCodeStart":434,"sourceCodeEnd":470,"githubUrl":"https://github.com/composer/composer/blob/c435d285c9120efdca35696769c72ea9fdcc0466/src/Composer/Downloader/DownloadManager.php#L434-L470","documentation":"Thrown by DownloadManager::getAvailableSources (a private method feeding install/update source selection). It builds a $sources array from sourceType and distType; if both are empty, the package has nothing to download and the InvalidArgumentException fires. Distinguishes from error 232: that is about installationSource being unset, this is about the package lacking source AND dist entirely.","triggerScenarios":"A package declares neither a source (git/svn/hg) nor a dist (zip/tar) — i.e. getSourceType() and getDistType() are both empty/null. Reached during install/update when Composer tries to determine which source(s) to offer.","commonSituations":"A provider/replace-only or virtual package that was mistakenly treated as installable. A custom repository serving packages without dist/source metadata. A metapackage mishandled upstream of the call.","solutions":["Confirm the package is meant to be installed (not just provided/replaced); if it's virtual, don't install it.","Ensure the repository serving the package includes a dist or source block in its metadata.","If the package is yours, publish a tag/release so a dist is generated, or add source info.","Use 'composer why <pkg>' to verify the dependency isn't pulling a stub package."],"exampleFix":"// before - package.json-style entry with no source/dist\n{\"name\": \"vendor/pkg\", \"version\": \"1.0.0\"}\n// after - add a dist\n{\"name\": \"vendor/pkg\", \"version\": \"1.0.0\", \"dist\": {\"url\": \"https://.../pkg.zip\", \"type\": \"zip\"}}","handlingStrategy":"validation","validationCode":"if ($package->getType() !== 'metapackage'\n    && empty($package->getSourceType())\n    && empty($package->getDistType())) {\n    throw new InvalidArgumentException('Package ' . $package->getName() . ' has neither source nor dist');\n}","typeGuard":"function hasSourceOrDist(\\Composer\\Package\\PackageInterface $p): bool {\n    return !empty($p->getSourceType()) || !empty($p->getDistType());\n}","tryCatchPattern":null,"preventionTips":["Serve complete dist/source metadata from custom repositories.","Mark virtual/provide-only packages as metapackage if they aren't installable.","Publish releases so dist artifacts exist."],"tags":["downloader","package","metadata","validation"],"backgroundTag":null,"analyzedSha":"c435d285c9120efdca35696769c72ea9fdcc0466","analyzedAt":"2026-08-07T18:58:23.525Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}