Compare commits

...

4 Commits

Author SHA1 Message Date
github-actions[bot]
ed9d5939e6 chore(release): version packages (#8479)
* chore(release): version packages

* fix

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-10-15 22:55:13 +04:00
shadcn
b52ec12f1e fix(shadcn): universal items handling (#8478)
* fix(shadcn): universal items handling

* chore: add changeset
2025-10-15 22:27:12 +04:00
Igor S. Zizinio
2ab9bff4bb Fix formatting of Badge variant prop in documentation (#8477) 2025-10-15 22:09:06 +04:00
Shaban Haider
2f6b51fa0a feat: add @efferd-ui in trusted registries (#8474) 2025-10-15 22:03:39 +04:00
9 changed files with 89 additions and 18 deletions

View File

@@ -49,7 +49,7 @@ import { Badge } from "@/components/ui/badge"
```
```tsx
<Badge variant="default |outline | secondary | destructive">Badge</Badge>
<Badge variant="default | outline | secondary | destructive">Badge</Badge>
```
### Link

View File

@@ -89,7 +89,7 @@
"recharts": "2.15.1",
"rehype-pretty-code": "^0.14.1",
"rimraf": "^6.0.1",
"shadcn": "3.4.1",
"shadcn": "3.4.2",
"shiki": "^1.10.1",
"sonner": "^2.0.0",
"tailwind-merge": "^3.0.1",

View File

@@ -15,6 +15,7 @@
"@bucharitesh": "https://bucharitesh.in/r/{name}.json",
"@clerk": "https://clerk.com/r/{name}.json",
"@cult-ui": "https://cult-ui.com/r/{name}.json",
"@efferd-ui": "https://ui.efferd.com/r/{name}.json",
"@eldoraui": "https://eldoraui.site/r/{name}.json",
"@elements": "https://tryelements.dev/r/{name}.json",
"@elevenlabs-ui": "https://ui.elevenlabs.io/r/{name}.json",

View File

@@ -88,7 +88,7 @@
"react-resizable-panels": "^2.0.22",
"react-wrap-balancer": "^0.4.1",
"recharts": "2.12.7",
"shadcn": "3.4.1",
"shadcn": "3.4.2",
"sharp": "^0.32.6",
"sonner": "^1.2.3",
"swr": "2.2.6-beta.3",

View File

@@ -1,5 +1,11 @@
# @shadcn/ui
## 3.4.2
### Patch Changes
- [#8478](https://github.com/shadcn-ui/ui/pull/8478) [`b52ec12f1e22cf89270bf3d931f5b7544e4b80b4`](https://github.com/shadcn-ui/ui/commit/b52ec12f1e22cf89270bf3d931f5b7544e4b80b4) Thanks [@shadcn](https://github.com/shadcn)! - fix regression with universal item detection
## 3.4.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "shadcn",
"version": "3.4.1",
"version": "3.4.2",
"description": "Add components to your apps.",
"publishConfig": {
"access": "public"

View File

@@ -144,6 +144,7 @@ describe("isLocalFile", () => {
describe("isUniversalRegistryItem", () => {
it("should return true when all files have targets with registry:file type", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{
path: "file1.ts",
@@ -160,7 +161,26 @@ describe("isUniversalRegistryItem", () => {
expect(isUniversalRegistryItem(registryItem)).toBe(true)
})
it("should return true for any registry item type if all files are registry:file with targets", () => {
it("should return true when registry item type is registry:file and all files have targets", () => {
const registryItem = {
type: "registry:file" as const,
files: [
{
path: "file1.ts",
target: "src/file1.ts",
type: "registry:file" as const,
},
{
path: "file2.ts",
target: "src/utils/file2.ts",
type: "registry:item" as const,
},
],
}
expect(isUniversalRegistryItem(registryItem)).toBe(true)
})
it("should return false for any registry item type other than registry:item or registry:file", () => {
const registryItem = {
type: "registry:ui" as const,
files: [
@@ -171,11 +191,12 @@ describe("isUniversalRegistryItem", () => {
},
],
}
expect(isUniversalRegistryItem(registryItem)).toBe(true)
expect(isUniversalRegistryItem(registryItem)).toBe(false)
})
it("should return false when some files lack targets", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{
path: "file1.ts",
@@ -190,6 +211,7 @@ describe("isUniversalRegistryItem", () => {
it("should return false when files have non-registry:file type", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{
path: "file1.ts",
@@ -208,6 +230,7 @@ describe("isUniversalRegistryItem", () => {
it("should return false when no files have targets", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{ path: "file1.ts", target: "", type: "registry:file" as const },
{ path: "file2.ts", target: "", type: "registry:file" as const },
@@ -216,18 +239,44 @@ describe("isUniversalRegistryItem", () => {
expect(isUniversalRegistryItem(registryItem)).toBe(false)
})
it("should return true when files array is empty", () => {
it("should return true when files array is empty and type is registry:item", () => {
const registryItem = {
type: "registry:item" as const,
files: [],
}
expect(isUniversalRegistryItem(registryItem)).toBe(true)
})
it("should return true when files is undefined", () => {
const registryItem = {}
it("should return true when files is undefined and type is registry:item", () => {
const registryItem = {
type: "registry:item" as const,
}
expect(isUniversalRegistryItem(registryItem)).toBe(true)
})
it("should return false when type is registry:style", () => {
const registryItem = {
type: "registry:style" as const,
files: [],
}
expect(isUniversalRegistryItem(registryItem)).toBe(false)
})
it("should return false when type is registry:ui", () => {
const registryItem = {
type: "registry:ui" as const,
files: [],
}
expect(isUniversalRegistryItem(registryItem)).toBe(false)
})
it("should return false when files is undefined and type is not registry:item or registry:file", () => {
const registryItem = {
type: "registry:component" as const,
}
expect(isUniversalRegistryItem(registryItem)).toBe(false)
})
it("should return false when registryItem is null", () => {
expect(isUniversalRegistryItem(null)).toBe(false)
})
@@ -238,6 +287,7 @@ describe("isUniversalRegistryItem", () => {
it("should return false when target is null", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{
path: "file1.ts",
@@ -251,6 +301,7 @@ describe("isUniversalRegistryItem", () => {
it("should return false when target is undefined", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{
path: "file1.ts",
@@ -264,6 +315,7 @@ describe("isUniversalRegistryItem", () => {
it("should return false when files have registry:component type even with targets", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{
path: "component.tsx",
@@ -277,6 +329,7 @@ describe("isUniversalRegistryItem", () => {
it("should return false when files have registry:hook type even with targets", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{
path: "use-hook.ts",
@@ -290,6 +343,7 @@ describe("isUniversalRegistryItem", () => {
it("should return false when files have registry:lib type even with targets", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{
path: "utils.ts",
@@ -303,6 +357,7 @@ describe("isUniversalRegistryItem", () => {
it("should return true when all targets are non-empty strings for registry:file", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{ path: "file1.ts", target: " ", type: "registry:file" as const }, // whitespace is truthy
{ path: "file2.ts", target: "0", type: "registry:file" as const }, // "0" is truthy
@@ -313,6 +368,7 @@ describe("isUniversalRegistryItem", () => {
it("should handle real-world example with path traversal attempts for registry:file", () => {
const registryItem = {
type: "registry:item" as const,
files: [
{
path: "malicious.ts",
@@ -326,18 +382,18 @@ describe("isUniversalRegistryItem", () => {
},
],
}
// The function should still return true - path validation is handled elsewhere
// The function should still return true - path validation is handled elsewhere.
expect(isUniversalRegistryItem(registryItem)).toBe(true)
})
it("should return false when files have non-registry:file type in a UI registry item", () => {
it("should return false when registry item type is registry:ui", () => {
const registryItem = {
type: "registry:ui" as const,
files: [
{
path: "button.tsx",
target: "src/components/ui/button.tsx",
type: "registry:ui" as const, // Not registry:file
type: "registry:file" as const,
},
],
}

View File

@@ -267,14 +267,14 @@ export function isLocalFile(path: string) {
/**
* Check if a registry item is universal (framework-agnostic).
* A universal registry item must have all files with:
* 1. Explicit targets
* 2. Type "registry:file"
* A universal registry item must:
* 1. Have type "registry:item" or "registry:file"
* 2. If it has files, all files must have explicit targets and be type "registry:file" or "registry:item"
* It can be installed without framework detection or components.json.
*/
export function isUniversalRegistryItem(
registryItem:
| Pick<z.infer<typeof registryItemSchema>, "files">
| Pick<z.infer<typeof registryItemSchema>, "files" | "type">
| null
| undefined
): boolean {
@@ -282,8 +282,16 @@ export function isUniversalRegistryItem(
return false
}
if (
registryItem.type !== "registry:item" &&
registryItem.type !== "registry:file"
) {
return false
}
const files = registryItem.files ?? []
// If there are files, all must have targets and be of type registry:file or registry:item.
return files.every(
(file) =>
!!file.target &&

4
pnpm-lock.yaml generated
View File

@@ -331,7 +331,7 @@ importers:
specifier: ^6.0.1
version: 6.0.1
shadcn:
specifier: 3.4.1
specifier: 3.4.2
version: link:../../packages/shadcn
shiki:
specifier: ^1.10.1
@@ -611,7 +611,7 @@ importers:
specifier: 2.12.7
version: 2.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
shadcn:
specifier: 3.4.1
specifier: 3.4.2
version: link:../../packages/shadcn
sharp:
specifier: ^0.32.6