mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-30 08:04:18 +00:00
fix: bug in registries
This commit is contained in:
@@ -70,6 +70,8 @@ export const add = new Command()
|
||||
|
||||
await loadEnvFiles(options.cwd)
|
||||
|
||||
const isDryRun = options.dryRun || options.diff || options.view
|
||||
|
||||
let initialConfig = await getConfig(options.cwd)
|
||||
if (!initialConfig) {
|
||||
initialConfig = createConfig({
|
||||
@@ -103,8 +105,6 @@ export const add = new Command()
|
||||
itemType !== "registry:style" &&
|
||||
itemType !== "registry:base"
|
||||
|
||||
const isDryRun = options.dryRun || options.diff || options.view
|
||||
|
||||
if (isUniversalRegistryItem(registryItem) && !isDryRun) {
|
||||
await addComponents(components, initialConfig, options)
|
||||
return
|
||||
@@ -263,13 +263,14 @@ export const add = new Command()
|
||||
config,
|
||||
{
|
||||
silent: options.silent || hasNewRegistries,
|
||||
writeFile: !isDryRun,
|
||||
}
|
||||
)
|
||||
config = updatedConfig
|
||||
|
||||
// Dry-run mode: preview changes without writing files.
|
||||
// --diff and --view imply --dry-run.
|
||||
if (options.dryRun || options.diff || options.view) {
|
||||
if (isDryRun) {
|
||||
const dryRunSpinner = spinner("Resolving items.", {
|
||||
silent: options.silent,
|
||||
}).start()
|
||||
|
||||
@@ -26,7 +26,9 @@ function colorAction(action: DryRunFile["action"] | "update") {
|
||||
|
||||
// Format the shared header line.
|
||||
function formatHeader(componentNames: string[]) {
|
||||
return `${bold("┌")} ${bold(`shadcn add ${componentNames.join(", ")}`)} ${dim("(dry run)")}`
|
||||
return `${bold("┌")} ${bold(`shadcn add ${componentNames.join(", ")}`)} ${dim(
|
||||
"(dry run)"
|
||||
)}`
|
||||
}
|
||||
|
||||
// Check if a CSS path matches a filter.
|
||||
@@ -92,7 +94,9 @@ function formatSummaryOutput(result: DryRunResult, componentNames: string[]) {
|
||||
if (overwriteCount > 0) {
|
||||
lines.push(
|
||||
yellow(
|
||||
`⚠ ${overwriteCount} ${overwriteCount === 1 ? "file" : "files"} will be overwritten.`
|
||||
`⚠ ${overwriteCount} ${
|
||||
overwriteCount === 1 ? "file" : "files"
|
||||
} will be overwritten.`
|
||||
)
|
||||
)
|
||||
lines.push(dim("│"))
|
||||
@@ -107,7 +111,9 @@ function formatSummaryOutput(result: DryRunResult, componentNames: string[]) {
|
||||
}
|
||||
if (result.dependencies.length > 0) {
|
||||
summaryParts.push(
|
||||
`${result.dependencies.length} ${result.dependencies.length === 1 ? "dep" : "deps"}`
|
||||
`${result.dependencies.length} ${
|
||||
result.dependencies.length === 1 ? "dep" : "deps"
|
||||
}`
|
||||
)
|
||||
}
|
||||
if (result.css?.cssVarsCount) {
|
||||
@@ -154,7 +160,9 @@ function formatDiffOutput(
|
||||
|
||||
if (cssMatch && result.css) {
|
||||
lines.push(
|
||||
`${dim("├")} ${bold(result.css.path)} ${dim("(")}${colorAction(result.css.action)}${dim(")")}`
|
||||
`${dim("├")} ${bold(result.css.path)} ${dim("(")}${colorAction(
|
||||
result.css.action
|
||||
)}${dim(")")}`
|
||||
)
|
||||
|
||||
if (result.css.action === "create" || !result.css.existingContent) {
|
||||
@@ -183,7 +191,9 @@ function formatDiffOutput(
|
||||
// Format a single file's diff block.
|
||||
function formatFileDiff(file: DryRunFile, lines: string[]) {
|
||||
lines.push(
|
||||
`${dim("├")} ${bold(file.path)} ${dim("(")}${colorAction(file.action)}${dim(")")}`
|
||||
`${dim("├")} ${bold(file.path)} ${dim("(")}${colorAction(file.action)}${dim(
|
||||
")"
|
||||
)}`
|
||||
)
|
||||
|
||||
if (file.action === "skip") {
|
||||
@@ -225,7 +235,9 @@ function formatViewOutput(
|
||||
for (const file of filesToView) {
|
||||
const contentLines = file.content.split("\n")
|
||||
lines.push(
|
||||
`${dim("├")} ${bold(file.path)} ${dim("(")}${colorAction(file.action)}${dim(")")} ${dim(`${contentLines.length} lines`)}`
|
||||
`${dim("├")} ${bold(file.path)} ${dim("(")}${colorAction(
|
||||
file.action
|
||||
)}${dim(")")} ${dim(`${contentLines.length} lines`)}`
|
||||
)
|
||||
pushContentBox(lines, contentLines)
|
||||
lines.push(dim("│"))
|
||||
@@ -234,7 +246,9 @@ function formatViewOutput(
|
||||
if (cssMatch && result.css) {
|
||||
const contentLines = result.css.content.split("\n")
|
||||
lines.push(
|
||||
`${dim("├")} ${bold(result.css.path)} ${dim("(")}${colorAction(result.css.action)}${dim(")")} ${dim(`${contentLines.length} lines`)}`
|
||||
`${dim("├")} ${bold(result.css.path)} ${dim("(")}${colorAction(
|
||||
result.css.action
|
||||
)}${dim(")")} ${dim(`${contentLines.length} lines`)}`
|
||||
)
|
||||
pushContentBox(lines, contentLines)
|
||||
lines.push(dim("│"))
|
||||
@@ -285,8 +299,8 @@ function formatFilesSection(result: DryRunResult, lines: string[]) {
|
||||
file.action === "create"
|
||||
? green
|
||||
: file.action === "overwrite"
|
||||
? yellow
|
||||
: dim
|
||||
? yellow
|
||||
: dim
|
||||
|
||||
const pathStr = file.action === "skip" ? dim(file.path) : file.path
|
||||
|
||||
@@ -319,7 +333,9 @@ function formatCssSection(result: DryRunResult, lines: string[]) {
|
||||
|
||||
if (result.css.cssVarsCount > 0) {
|
||||
lines.push(
|
||||
`${dim("│")} ${green("+")} ${result.css.cssVarsCount} CSS variables added to ${cyan(result.css.path)}`
|
||||
`${dim("│")} ${green("+")} ${
|
||||
result.css.cssVarsCount
|
||||
} CSS variables added to ${cyan(result.css.path)}`
|
||||
)
|
||||
} else {
|
||||
lines.push(`${dim("│")} ${green("+")} Updated ${cyan(result.css.path)}`)
|
||||
@@ -416,10 +432,7 @@ function computeUnifiedDiff(
|
||||
return [dim(" No changes.")]
|
||||
}
|
||||
|
||||
const output: string[] = [
|
||||
dim(`--- a/${filePath}`),
|
||||
dim(`+++ b/${filePath}`),
|
||||
]
|
||||
const output: string[] = [dim(`--- a/${filePath}`), dim(`+++ b/${filePath}`)]
|
||||
|
||||
// Use the actual new file lines for display.
|
||||
const newLines = newStr.split("\n")
|
||||
@@ -439,7 +452,9 @@ function computeUnifiedDiff(
|
||||
|
||||
output.push(
|
||||
cyan(
|
||||
`@@ -${hunk.oldStart},${contextCount + removedCount} +${hunk.newStart},${contextCount + addedCount} @@`
|
||||
`@@ -${hunk.oldStart},${contextCount + removedCount} +${
|
||||
hunk.newStart
|
||||
},${contextCount + addedCount} @@`
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
123
packages/shadcn/test/utils/registries.test.ts
Normal file
123
packages/shadcn/test/utils/registries.test.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { afterEach, describe, expect, test, vi } from "vitest"
|
||||
|
||||
import type { Config } from "../../src/utils/get-config"
|
||||
|
||||
// Mock dependencies.
|
||||
vi.mock("../../src/registry/namespaces", () => ({
|
||||
resolveRegistryNamespaces: vi.fn().mockResolvedValue(["@foo"]),
|
||||
}))
|
||||
|
||||
vi.mock("../../src/registry/api", () => ({
|
||||
getRegistriesIndex: vi.fn().mockResolvedValue({
|
||||
"@foo": "https://foo.com/r/{name}.json",
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock("../../src/utils/spinner", () => ({
|
||||
spinner: vi.fn().mockReturnValue({
|
||||
start: vi.fn().mockReturnValue({
|
||||
succeed: vi.fn(),
|
||||
fail: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock("fs-extra", () => ({
|
||||
default: {
|
||||
writeFile: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
}))
|
||||
|
||||
import { ensureRegistriesInConfig } from "../../src/utils/registries"
|
||||
import fs from "fs-extra"
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
const baseConfig: Config = {
|
||||
$schema: "",
|
||||
style: "new-york",
|
||||
tailwind: {
|
||||
config: "",
|
||||
css: "",
|
||||
baseColor: "",
|
||||
cssVariables: true,
|
||||
prefix: "",
|
||||
},
|
||||
rsc: false,
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
utils: "@/lib/utils",
|
||||
ui: "@/components/ui",
|
||||
lib: "@/lib",
|
||||
hooks: "@/hooks",
|
||||
},
|
||||
registries: {},
|
||||
resolvedPaths: {
|
||||
cwd: "/tmp/test-project",
|
||||
tailwindConfig: "",
|
||||
tailwindCss: "",
|
||||
utils: "",
|
||||
components: "",
|
||||
lib: "",
|
||||
hooks: "",
|
||||
ui: "",
|
||||
},
|
||||
}
|
||||
|
||||
describe("ensureRegistriesInConfig", () => {
|
||||
test("does not write to disk when writeFile is false", async () => {
|
||||
const { config, newRegistries } = await ensureRegistriesInConfig(
|
||||
["@foo/bar"],
|
||||
baseConfig,
|
||||
{ writeFile: false }
|
||||
)
|
||||
|
||||
// Should still return the updated config with new registries.
|
||||
expect(newRegistries).toEqual(["@foo"])
|
||||
expect(config.registries?.["@foo"]).toBe(
|
||||
"https://foo.com/r/{name}.json"
|
||||
)
|
||||
|
||||
// Should NOT have written to disk.
|
||||
expect(fs.writeFile).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("writes to disk when writeFile is true", async () => {
|
||||
await ensureRegistriesInConfig(["@foo/bar"], baseConfig, {
|
||||
writeFile: true,
|
||||
})
|
||||
|
||||
expect(fs.writeFile).toHaveBeenCalledTimes(1)
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expect.stringContaining("components.json"),
|
||||
expect.any(String),
|
||||
"utf-8"
|
||||
)
|
||||
})
|
||||
|
||||
test("writes to disk by default (writeFile not specified)", async () => {
|
||||
await ensureRegistriesInConfig(["@foo/bar"], baseConfig)
|
||||
|
||||
expect(fs.writeFile).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("does not write when no new registries are found", async () => {
|
||||
const configWithRegistry: Config = {
|
||||
...baseConfig,
|
||||
registries: {
|
||||
"@foo": "https://foo.com/r/{name}.json",
|
||||
},
|
||||
}
|
||||
|
||||
await ensureRegistriesInConfig(["@foo/bar"], configWithRegistry, {
|
||||
writeFile: true,
|
||||
})
|
||||
|
||||
// No new registries, so no write.
|
||||
expect(fs.writeFile).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user