mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-02-09 02:49:29 +08:00
fix(shadcn): resolver for url (#9054)
This commit is contained in:
5
.changeset/solid-rings-cover.md
Normal file
5
.changeset/solid-rings-cover.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
fix resolver for url
|
||||
18
.github/workflows/prerelease.yml
vendored
18
.github/workflows/prerelease.yml
vendored
@@ -7,6 +7,11 @@ on:
|
||||
types: [labeled]
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
prerelease:
|
||||
if: |
|
||||
@@ -18,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -28,22 +33,21 @@ jobs:
|
||||
version: 9.0.6
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Update npm for OIDC support
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Modify package.json version
|
||||
run: node .github/version-script-beta.js
|
||||
|
||||
- name: Authenticate to NPM
|
||||
run: echo "//registry.npmjs.org/:_authToken=$NPM_ACCESS_TOKEN" >> packages/shadcn/.npmrc
|
||||
env:
|
||||
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||
|
||||
- name: Publish Beta to NPM
|
||||
run: pnpm pub:beta
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/shadcn/ui.git",
|
||||
"url": "https://github.com/shadcn-ui/ui.git",
|
||||
"directory": "packages/shadcn"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -230,7 +230,7 @@ export const init = new Command()
|
||||
item.extends === "none" ? false : options.baseStyle
|
||||
}
|
||||
|
||||
if (item?.type === "registry:style" && !options.baseStyle) {
|
||||
if (item?.type === "registry:style") {
|
||||
// Set a default base color so we're not prompted.
|
||||
// The style will extend or override it.
|
||||
options.baseColor = "neutral"
|
||||
|
||||
@@ -342,16 +342,12 @@ describe("buildHeadersFromRegistryConfig", () => {
|
||||
})
|
||||
|
||||
describe("buildUrlAndHeadersForRegistryItem", () => {
|
||||
it("should default to @shadcn registry for non-registry items", () => {
|
||||
it("should resolve non-registry items through @shadcn registry", () => {
|
||||
const input = "button"
|
||||
const config = {
|
||||
registries: {
|
||||
"@shadcn": "https://ui.shadcn.com/r/{name}.json",
|
||||
},
|
||||
} as any
|
||||
const result = buildUrlAndHeadersForRegistryItem(input, config)
|
||||
expect(result).toEqual({
|
||||
url: "https://ui.shadcn.com/r/button.json",
|
||||
const config = {} as any
|
||||
// Non-prefixed items are resolved through the built-in @shadcn registry
|
||||
expect(buildUrlAndHeadersForRegistryItem(input, config)).toEqual({
|
||||
url: "https://ui.shadcn.com/r/styles/{style}/button.json",
|
||||
headers: {},
|
||||
})
|
||||
})
|
||||
@@ -362,14 +358,6 @@ describe("buildUrlAndHeadersForRegistryItem", () => {
|
||||
}).toThrow('Unknown registry "@unknown"')
|
||||
})
|
||||
|
||||
it("should throw error when @shadcn is not configured for non-registry items", () => {
|
||||
const input = "button"
|
||||
const config = {} as any
|
||||
expect(() => {
|
||||
buildUrlAndHeadersForRegistryItem(input, config)
|
||||
}).toThrow('Unknown registry "@shadcn"')
|
||||
})
|
||||
|
||||
it("should resolve registry items with string config", () => {
|
||||
const config = {
|
||||
registries: {
|
||||
@@ -452,46 +440,18 @@ describe("buildUrlAndHeadersForRegistryItem", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("should default to @shadcn registry for URLs and local files", () => {
|
||||
const config = {
|
||||
registries: {
|
||||
"@shadcn": "https://ui.shadcn.com/r/{name}.json",
|
||||
},
|
||||
} as any
|
||||
|
||||
// URLs default to @shadcn registry
|
||||
const urlResult = buildUrlAndHeadersForRegistryItem(
|
||||
"https://example.com/button",
|
||||
config
|
||||
)
|
||||
expect(urlResult).toEqual({
|
||||
url: "https://ui.shadcn.com/r/https://example.com/button.json",
|
||||
headers: {},
|
||||
})
|
||||
|
||||
// Local files default to @shadcn registry
|
||||
const localResult = buildUrlAndHeadersForRegistryItem(
|
||||
"./local/button",
|
||||
config
|
||||
)
|
||||
expect(localResult).toEqual({
|
||||
url: "https://ui.shadcn.com/r/./local/button.json",
|
||||
headers: {},
|
||||
})
|
||||
})
|
||||
|
||||
it("should throw error when @shadcn is not configured for URLs and local files", () => {
|
||||
it("should handle URLs and local files", () => {
|
||||
const config = { registries: {} } as any
|
||||
|
||||
// URLs should throw error when @shadcn is not configured
|
||||
expect(() => {
|
||||
// URLs should return null (not registry items)
|
||||
expect(
|
||||
buildUrlAndHeadersForRegistryItem("https://example.com/button", config)
|
||||
}).toThrow('Unknown registry "@shadcn"')
|
||||
).toBeNull()
|
||||
|
||||
// Local files should throw error when @shadcn is not configured
|
||||
expect(() => {
|
||||
// Local files should return null (not registry items)
|
||||
expect(
|
||||
buildUrlAndHeadersForRegistryItem("./local/button", config)
|
||||
}).toThrow('Unknown registry "@shadcn"')
|
||||
).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { REGISTRY_URL } from "@/src/registry/constants"
|
||||
import { BUILTIN_REGISTRIES, REGISTRY_URL } from "@/src/registry/constants"
|
||||
import { expandEnvVars } from "@/src/registry/env"
|
||||
import { RegistryNotConfiguredError } from "@/src/registry/errors"
|
||||
import { parseRegistryAndItemFromString } from "@/src/registry/parser"
|
||||
import { isUrl } from "@/src/registry/utils"
|
||||
import { isLocalFile, isUrl } from "@/src/registry/utils"
|
||||
import { validateRegistryConfig } from "@/src/registry/validator"
|
||||
import { registryConfigItemSchema } from "@/src/schema"
|
||||
import { Config } from "@/src/utils/get-config"
|
||||
@@ -14,17 +14,26 @@ const ENV_VAR_PATTERN = /\${(\w+)}/g
|
||||
const QUERY_PARAM_SEPARATOR = "?"
|
||||
const QUERY_PARAM_DELIMITER = "&"
|
||||
|
||||
function isLocalPath(path: string) {
|
||||
return path.startsWith("./") || path.startsWith("/")
|
||||
}
|
||||
|
||||
export function buildUrlAndHeadersForRegistryItem(
|
||||
name: string,
|
||||
config?: Config
|
||||
) {
|
||||
let { registry, item } = parseRegistryAndItemFromString(name)
|
||||
|
||||
// If no registry prefix, check if it's a URL or local path.
|
||||
// These should be handled directly, not through a registry.
|
||||
if (!registry) {
|
||||
if (isUrl(name) || isLocalFile(name) || isLocalPath(name)) {
|
||||
return null
|
||||
}
|
||||
registry = "@shadcn"
|
||||
}
|
||||
|
||||
const registries = config?.registries || {}
|
||||
const registries = { ...BUILTIN_REGISTRIES, ...config?.registries }
|
||||
const registryConfig = registries[registry]
|
||||
if (!registryConfig) {
|
||||
throw new RegistryNotConfiguredError(registry)
|
||||
|
||||
@@ -65,13 +65,18 @@ describe("resolveRegistryItemsFromRegistries", () => {
|
||||
expect(setRegistryHeaders).toHaveBeenCalledWith({})
|
||||
})
|
||||
|
||||
it("should return non-registry items unchanged", () => {
|
||||
it("should resolve non-registry items through @shadcn registry", () => {
|
||||
const items = ["button", "card", "dialog"]
|
||||
const config = { registries: {} } as any
|
||||
|
||||
const result = resolveRegistryItemsFromRegistries(items, config)
|
||||
|
||||
expect(result).toEqual(items)
|
||||
// Non-prefixed items are resolved through the built-in @shadcn registry
|
||||
expect(result).toEqual([
|
||||
"https://ui.shadcn.com/r/styles/{style}/button.json",
|
||||
"https://ui.shadcn.com/r/styles/{style}/card.json",
|
||||
"https://ui.shadcn.com/r/styles/{style}/dialog.json",
|
||||
])
|
||||
expect(setRegistryHeaders).toHaveBeenCalledWith({})
|
||||
})
|
||||
|
||||
@@ -137,10 +142,11 @@ describe("resolveRegistryItemsFromRegistries", () => {
|
||||
|
||||
const result = resolveRegistryItemsFromRegistries(items, config)
|
||||
|
||||
// Non-registry items (button, dialog) are resolved through the built-in @shadcn registry
|
||||
expect(result).toEqual([
|
||||
"button",
|
||||
"https://ui.shadcn.com/r/styles/{style}/button.json",
|
||||
"https://v0.dev/chat/b/card/json",
|
||||
"dialog",
|
||||
"https://ui.shadcn.com/r/styles/{style}/dialog.json",
|
||||
"https://api.com/table.json",
|
||||
])
|
||||
expect(setRegistryHeaders).toHaveBeenCalledWith({
|
||||
|
||||
@@ -11,7 +11,7 @@ const testConfig: Config = {
|
||||
tailwind: {
|
||||
baseColor: "neutral",
|
||||
cssVariables: true,
|
||||
config: "tailwind.config.ts",
|
||||
config: "",
|
||||
css: "tailwind.css",
|
||||
},
|
||||
aliases: {
|
||||
@@ -25,7 +25,7 @@ const testConfig: Config = {
|
||||
ui: "/ui",
|
||||
lib: "/lib",
|
||||
hooks: "/hooks",
|
||||
tailwindConfig: "tailwind.config.ts",
|
||||
tailwindConfig: "",
|
||||
tailwindCss: "tailwind.css",
|
||||
},
|
||||
}
|
||||
@@ -572,9 +572,9 @@ export function Component() {
|
||||
`)
|
||||
})
|
||||
|
||||
test("throws InvalidConfigIconLibraryError for invalid icon library", async () => {
|
||||
await expect(
|
||||
transform(
|
||||
test("does not transform for invalid icon library", async () => {
|
||||
expect(
|
||||
await transform(
|
||||
{
|
||||
filename: "test.tsx",
|
||||
raw: `import * as React from "react"
|
||||
@@ -590,12 +590,14 @@ export function Component() {
|
||||
},
|
||||
[transformIcons]
|
||||
)
|
||||
).rejects.toMatchObject({
|
||||
name: "InvalidConfigIconLibraryError",
|
||||
message:
|
||||
'Invalid icon library "invalid-library". Valid options are: lucide, tabler, hugeicons',
|
||||
code: "INVALID_CONFIG",
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
"import * as React from "react"
|
||||
import { IconPlaceholder } from "@/app/(create)/create/components/icon-placeholder"
|
||||
|
||||
export function Component() {
|
||||
return <IconPlaceholder lucide="CheckIcon" />
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test("does not forward library-specific props (lucide)", async () => {
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { iconLibraries, type IconLibraryName } from "@/src/icons/libraries"
|
||||
import { InvalidConfigIconLibraryError } from "@/src/registry/errors"
|
||||
import { Transformer } from "@/src/utils/transformers"
|
||||
import { SourceFile, SyntaxKind } from "ts-morph"
|
||||
|
||||
export const transformIcons: Transformer = async ({ sourceFile, config }) => {
|
||||
const iconLibrary = config.iconLibrary
|
||||
if (!iconLibrary) {
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
if (!(iconLibrary in iconLibraries)) {
|
||||
throw new InvalidConfigIconLibraryError(
|
||||
iconLibrary,
|
||||
Object.keys(iconLibraries)
|
||||
)
|
||||
// Fail silently if the icon library is not supported.
|
||||
// This is for legacy icon libraries.
|
||||
if (!iconLibrary || !(iconLibrary in iconLibraries)) {
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
const targetLibrary = iconLibrary as IconLibraryName
|
||||
|
||||
@@ -75,7 +75,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.ts",
|
||||
tailwindCssFile: "src/styles/globals.css",
|
||||
tailwindVersion: "v3",
|
||||
frameworkVersion: "14.2.4",
|
||||
frameworkVersion: "14.2.35",
|
||||
aliasPrefix: "~",
|
||||
},
|
||||
},
|
||||
@@ -89,7 +89,7 @@ describe("get project info", async () => {
|
||||
tailwindConfigFile: "tailwind.config.ts",
|
||||
tailwindCssFile: "src/styles/globals.css",
|
||||
tailwindVersion: "v3",
|
||||
frameworkVersion: "13.4.2",
|
||||
frameworkVersion: "14.2.35",
|
||||
aliasPrefix: "~",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -114,7 +114,7 @@ describe("resolveFilePath", () => {
|
||||
type: "registry:ui",
|
||||
target: "design-system/ui/button.tsx",
|
||||
},
|
||||
resolvedPath: "/foo/bar/src/create-system/ui/button.tsx",
|
||||
resolvedPath: "/foo/bar/src/design-system/ui/button.tsx",
|
||||
projectInfo: {
|
||||
isSrcDir: true,
|
||||
},
|
||||
|
||||
@@ -404,13 +404,6 @@ describe("shadcn search", () => {
|
||||
expect(output.stdout).toContain('Unknown registry "@test-123"')
|
||||
})
|
||||
|
||||
it("should handle empty registry name", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app-init")
|
||||
const output = await npxShadcn(fixturePath, ["search", "@"])
|
||||
|
||||
expect(output.stdout).toContain("The item at @/registry was not found.")
|
||||
})
|
||||
|
||||
it("should handle namespace without @ prefix", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app-init")
|
||||
const output = await npxShadcn(fixturePath, ["search", "one"])
|
||||
|
||||
Reference in New Issue
Block a user