+
Skip to content

Conversation

2qp
Copy link

@2qp 2qp commented Oct 9, 2025

PR: Improve Registry Path Handling in Monorepo Environments

🛠️ Changes Summary

utils.ts

  • add Pick<RegistryItem, "files" | "dependencies"> to replace Pick<z.infer<typeof registryItemSchema>, "files" | "dependencies">

  • add removeFiles?: string[] as a return type in recursivelyResolveFileImports

  • add const stat = await getStatsOrNonFile(resolvedFilePath) to replace const stat = await fs.stat(resolvedFilePath)

  • add fallback logic into handle case under if (!stat.isFile()) { and return { dependencies: [], files: [originalFile], removeFiles: [filePath] }

build.ts

  • add logic in resolveRegistryItems to remove files from registry.items whose paths are listed in results.removeFiles.

Reason

  • existing RegistryItem = z.infer<typeof registryItemSchema> is already in the registry/schema.

  • removing unresolvable file path from registry item before it catches in Error reading file in registry build: since new resolved file path is added as a replacement for it.

  • getStatsOrNonFile suppresses ENOENT: no such file or directory, stat and avoid crash, so that flow could try to get an alternative path.

  • aliases like @/components/somewhere/some-thing.tsx usually fails here. so it could try to get an alternative path by using tryAlternativePath which leverages tsconfig-paths. it will resolve the first correct matching file path.

  • since alternative path is added, remaining failed file path is added to removeFiles to get removed from registry.items


PR roadmap introduces several refactors and new utility functions to improve how the shadcn registry:build CLI handles failed aliased import paths, especially in monorepo setups using path aliases (e.g. @/components/...).

🐛 Problem

In monorepos, adding a registry component like:

{
  "files": [
    {
      "path": "@/components/rhf/rhf-text-field.tsx",
      "type": "registry:component"
    }
  ]
}

...and running:

pnpm shadcn registry:build

Would fail with:

ENOENT: no such file or directory, stat '/absolute/path/to/ui/apps/registry/@/components/ui/rhf/rhf-text-field.tsx'

fs.stat() is called before the alias is resolved by tsconfig-paths, causing the CLI to crash ungracefully.


💡 Why This Happens

In a monorepo, files from multiple workspaces may be referenced using aliases (@, @ui, etc.). If these aliases aren't fully resolved to disk paths, the CLI crashes when trying to access them directly via fs.stat.

The current logic fails here:

// packages/shadcn/src/registry/utils.ts
const stat = await fs.stat(resolvedFilePath);
if (!stat.isFile()) {
  // Optionally log or handle this case
  return { dependencies: [], files: [] };
}

When fs.stat() is called on an invalid or unresolved path, it throws an ENOENT error, causing the CLI to crash ungracefully.


🧭 Roadmap

part of a multi step effort to enable the registry CLI to handle path aliases gracefully in monorepo environments.

Each change is isolated for clean diffs.


🧱 Directory Structure (for Context)

my monorepo structure to understand the issue:

./
├── apps/registry/ # registry
│   ├── components.json, package.json, registry.json
│   ├── public/r/ → registry.json, rhf-text-field.json, theme.json
│   └── src/
│       ├── app/
│       │   ├── (registry)/
│       │   └── demo/[name]/ → components/rhf-text-field.tsx, ui/ # demo
│       ├── components/ → registry/, rhf-text-field.tsx # example
│       ├── content/, hooks/, layouts/
│       └── lib/ → highlight-code.ts, products.ts, registry.ts, utils.ts
│       ├── tsconfig.json, tsconfig.tsbuildinfo
├── package.json
├── packages/
│   ├── eslint-config/, hooks/, typescript-config/
│   ├── shadcn-ui/ # shadcn-ui repo
│   │   ├── components.json, package.json, tsconfig.json
│   │   └── src/
│   │       ├── components/ui/ → button.tsx, form.tsx, input.tsx, label.tsx
│   │       ├── hooks/, lib/utils.ts, styles/globals.css
│   ├── shadcn-ui-extended/ # extended component repo `rhf-text-field.tsx`
│   │   ├── package.json, tsconfig.json
│   │   └── src/
│   │       ├── components/ → rhf/rhf-text-field.tsx, ui/
│   │       └── data/, helpers/, hooks/, lib/, utils/
│   └── ui/turbo/generators/
└── pnpm-lock.yaml, pnpm-workspace.yaml, turbo.json

tsconfig.json

    "paths": {
      // ...
      "@/components/ui/*": [
        "../../packages/shadcn-ui/src/components/ui/*", // primary: shadcn-ui package
        "src/components/ui/*" // fallback: local overrides
      ],

      "@/components/rhf/*": [
        "../../packages/shadcn-ui-extended/src/components/rhf/*", // shadcn-ui-extended
        "src/components/rhf/*" // local overrides
      ],
    }

📚 Notes

  • tsconfig-paths is used, but current fs.stat() happens before alias resolution in some cases.
  • roadmap feat provides resilience without adding external dependencies or needing a separate alias parser (yet).

🔗 Follow-Ups

  • Log warnings for files that fail to resolve, even after retry/fallback attempts.
  • Log a detailed warning when the same file name is found in multiple tsconfig-resolved paths, including all matching paths and the one thats chosen.
  • Support a configurable conflict resolution mode "first-match", "last-match", "manual" via custom tsconfig extension.
  • Accept an optional registry or metadata file alongside user defined paths. leverage metadata to customize resolution behavior (matching, prioritization, overrides...).

2qp added 5 commits October 8, 2025 23:06
…to `utils/registry/determine-file-type.ts` avoid upcoming circular deps

- `createFileObject` (to be imported into `utils.ts`) will use `determineFileType`, which currently lives in `utils.ts`. - moving it out prevents circular imports between `utils.ts` and `createFileObject`.
…`createRegistryFile` utility

- improve modularity and upcoming reusability
- added Type alias `RegistryItemFile` of `registryItemFileSchema`
… fallback on `ENOENT`

- supresses crash `ENOENT: no such file or directory, stat`
…hOptions` util to resolve an alt path to a failed path

- added `tryAlternativePath` to leverage `resolvePathWithOptions` for resolving failed file paths provided by registry.

- common usecases are `path aliases` in `monorepos`.

- added `resolvePathWithOptions` for resolving module paths with configurable file extensions and file existence checks.
…e for non-file paths

- added fallback logic for cases where the resolved path is not a regular file.
- remove matching file paths from item.files based on removeFiles

- uses `getStatsOrNonFile` | `tryAlternativePath` | `determineFileType` | `createRegistryFile`
Copy link

vercel bot commented Oct 9, 2025

@2qp is attempting to deploy a commit to the shadcn-pro Team on Vercel.

A member of the Team first needs to authorize it.

@2qp 2qp marked this pull request as ready for review October 9, 2025 08:22
@2qp
Copy link
Author

2qp commented Oct 9, 2025

@shadcn, would appreciate your feedback when you have a moment :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载