@@ -1589,16 +1589,13 @@ function createCatalogDependencyResolver(
15891589 } ;
15901590 const workspacesObj =
15911591 pkg . workspaces && ! Array . isArray ( pkg . workspaces ) ? pkg . workspaces : undefined ;
1592- return ( catalogSpec , dependencyName ) => {
1593- const catalogName = catalogSpec . slice ( 'catalog:' . length ) ;
1594- if ( catalogName ) {
1595- return (
1596- workspacesObj ?. catalogs ?. [ catalogName ] ?. [ dependencyName ] ??
1597- pkg . catalogs ?. [ catalogName ] ?. [ dependencyName ]
1598- ) ;
1599- }
1600- return workspacesObj ?. catalog ?. [ dependencyName ] ?? pkg . catalog ?. [ dependencyName ] ;
1601- } ;
1592+ const fromWorkspaces = createCatalogDependencyResolverFromCatalogs (
1593+ workspacesObj ?. catalog ,
1594+ workspacesObj ?. catalogs ,
1595+ ) ;
1596+ const fromPkg = createCatalogDependencyResolverFromCatalogs ( pkg . catalog , pkg . catalogs ) ;
1597+ return ( catalogSpec , dependencyName ) =>
1598+ fromWorkspaces ( catalogSpec , dependencyName ) ?? fromPkg ( catalogSpec , dependencyName ) ;
16021599 }
16031600 return undefined ;
16041601}
@@ -1609,7 +1606,9 @@ function createCatalogDependencyResolverFromCatalogs(
16091606) : CatalogDependencyResolver {
16101607 return ( catalogSpec , dependencyName ) => {
16111608 const catalogName = catalogSpec . slice ( 'catalog:' . length ) ;
1612- if ( catalogName ) {
1609+ // pnpm/bun reserve `default` as the name of the top-level `catalog:` map,
1610+ // so `catalog:default` resolves there, not a named `catalogs` entry.
1611+ if ( catalogName && catalogName !== 'default' ) {
16131612 return catalogs ?. [ catalogName ] ?. [ dependencyName ] ;
16141613 }
16151614 return catalog ?. [ dependencyName ] ;
@@ -2795,21 +2794,35 @@ function rewriteAllImports(projectPath: string, silent = false, report?: Migrati
27952794/**
27962795 * Check if the project has an unsupported husky version (<9.0.0).
27972796 * Uses `semver.coerce` to handle ranges like `^8.0.0` → `8.0.0`.
2798- * When the specifier is not coercible (e.g. `"latest"`), falls back to
2799- * the installed version in node_modules via `detectPackageMetadata`.
2797+ * When the specifier is a catalog reference (e.g. `"catalog:"`), resolves
2798+ * it from the active package manager's catalog first — a `catalog:` spec is
2799+ * only meaningful to the manager that owns the workspace, so we never read a
2800+ * leftover/foreign catalog file. When it is still not coercible (e.g.
2801+ * `"latest"`), falls back to the installed version in node_modules via
2802+ * `detectPackageMetadata`.
28002803 * Returns a reason string if hooks migration should be skipped, or null
28012804 * if husky is absent or compatible.
28022805 */
28032806function checkUnsupportedHuskyVersion (
28042807 projectPath : string ,
28052808 deps : Record < string , string > | undefined ,
28062809 prodDeps : Record < string , string > | undefined ,
2810+ packageManager : PackageManager | undefined ,
28072811) : string | null {
28082812 const huskyVersion = deps ?. husky ?? prodDeps ?. husky ;
28092813 if ( ! huskyVersion ) {
28102814 return null ;
28112815 }
28122816 let coerced = semver . coerce ( huskyVersion ) ;
2817+ if ( coerced == null && packageManager != null && huskyVersion . startsWith ( 'catalog:' ) ) {
2818+ const resolved = createCatalogDependencyResolver ( projectPath , packageManager ) ?.(
2819+ huskyVersion ,
2820+ 'husky' ,
2821+ ) ;
2822+ if ( resolved ) {
2823+ coerced = semver . coerce ( resolved ) ;
2824+ }
2825+ }
28132826 if ( coerced == null ) {
28142827 const installed = detectPackageMetadata ( projectPath , 'husky' ) ;
28152828 if ( installed ) {
@@ -2881,9 +2894,10 @@ export function installGitHooks(
28812894 projectPath : string ,
28822895 silent = false ,
28832896 report ?: MigrationReport ,
2897+ packageManager ?: PackageManager ,
28842898) : boolean {
28852899 const oldHooksDir = getOldHooksDir ( projectPath ) ;
2886- if ( setupGitHooks ( projectPath , oldHooksDir , silent , report ) ) {
2900+ if ( setupGitHooks ( projectPath , oldHooksDir , silent , report , packageManager ) ) {
28872901 rewritePrepareScript ( projectPath ) ;
28882902 return true ;
28892903 }
@@ -2918,8 +2932,14 @@ export function getOldHooksDir(rootDir: string): string | undefined {
29182932 *
29192933 * These checks are deterministic and read-only — they do not modify
29202934 * the project in any way, making them safe to call before migration.
2935+ *
2936+ * `packageManager` is the project's detected manager; it scopes `catalog:`
2937+ * resolution to that manager's catalog so a foreign catalog file is ignored.
29212938 */
2922- export function preflightGitHooksSetup ( projectPath : string ) : string | null {
2939+ export function preflightGitHooksSetup (
2940+ projectPath : string ,
2941+ packageManager ?: PackageManager ,
2942+ ) : string | null {
29232943 const gitRoot = findGitRoot ( projectPath ) ;
29242944 if ( gitRoot && path . resolve ( projectPath ) !== path . resolve ( gitRoot ) ) {
29252945 return 'Subdirectory project detected — skipping git hooks setup. Configure hooks at the repository root.' ;
@@ -2936,7 +2956,7 @@ export function preflightGitHooksSetup(projectPath: string): string | null {
29362956 return `Detected ${ tool } — skipping git hooks setup. Please configure git hooks manually.` ;
29372957 }
29382958 }
2939- const huskyReason = checkUnsupportedHuskyVersion ( projectPath , deps , prodDeps ) ;
2959+ const huskyReason = checkUnsupportedHuskyVersion ( projectPath , deps , prodDeps , packageManager ) ;
29402960 if ( huskyReason ) {
29412961 return huskyReason ;
29422962 }
@@ -2956,8 +2976,9 @@ export function setupGitHooks(
29562976 oldHooksDir ?: string ,
29572977 silent = false ,
29582978 report ?: MigrationReport ,
2979+ packageManager ?: PackageManager ,
29592980) : boolean {
2960- const reason = preflightGitHooksSetup ( projectPath ) ;
2981+ const reason = preflightGitHooksSetup ( projectPath , packageManager ) ;
29612982 if ( reason ) {
29622983 warnMigration ( reason , report ) ;
29632984 return false ;
0 commit comments