Skip to content

Commit

Permalink
Merge branch 'main' into polymorphic-badge
Browse files Browse the repository at this point in the history
  • Loading branch information
anstapol authored Jan 20, 2025
2 parents f5982be + 5f7957a commit 6436242
Show file tree
Hide file tree
Showing 10 changed files with 642 additions and 117 deletions.
5 changes: 5 additions & 0 deletions .changeset/silent-rules-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"shadcn": minor
---

handle nested file path
5 changes: 5 additions & 0 deletions .changeset/swift-eyes-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"shadcn": minor
---

new registry:file type
2 changes: 1 addition & 1 deletion apps/www/lib/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ function getFileTarget(file: z.infer<typeof registryItemFileSchema>) {
}
}

return target
return target ?? ""
}

async function createTempSourceFile(filename: string) {
Expand Down
3 changes: 2 additions & 1 deletion apps/www/public/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"registry:ui",
"registry:hook",
"registry:theme",
"registry:page"
"registry:page",
"registry:file"
]
},
"description": {
Expand Down
20 changes: 17 additions & 3 deletions apps/www/public/schema/registry-item.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"registry:ui",
"registry:hook",
"registry:theme",
"registry:page"
"registry:page",
"registry:file"
]
},
"description": {
Expand Down Expand Up @@ -64,14 +65,27 @@
"registry:ui",
"registry:hook",
"registry:theme",
"registry:page"
"registry:page",
"registry:file"
]
},
"target": {
"type": "string"
}
},
"required": ["path", "type"]
"if": {
"properties": {
"type": {
"enum": ["registry:file", "registry:page"]
}
}
},
"then": {
"required": ["path", "type", "target"]
},
"else": {
"required": ["path", "type"]
}
}
},
"tailwind": {
Expand Down
34 changes: 0 additions & 34 deletions packages/shadcn/src/registry/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,40 +233,6 @@ export async function fetchRegistry(paths: string[]) {
}
}

export function getRegistryItemFileTargetPath(
file: z.infer<typeof registryItemFileSchema>,
config: Config,
override?: string
) {
if (override) {
return override
}

if (file.type === "registry:ui") {
return config.resolvedPaths.ui
}

if (file.type === "registry:lib") {
return config.resolvedPaths.lib
}

if (file.type === "registry:block" || file.type === "registry:component") {
return config.resolvedPaths.components
}

if (file.type === "registry:hook") {
return config.resolvedPaths.hooks
}

// TODO: we put this in components for now.
// We should move this to pages as per framework.
if (file.type === "registry:page") {
return config.resolvedPaths.components
}

return config.resolvedPaths.components
}

export async function registryResolveItemsTree(
names: z.infer<typeof registryItemSchema>["name"][],
config: Config
Expand Down
24 changes: 17 additions & 7 deletions packages/shadcn/src/registry/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,31 @@ export const registryItemTypeSchema = z.enum([
"registry:component",
"registry:ui",
"registry:hook",
"registry:theme",
"registry:page",
"registry:file",

// Internal use only
"registry:theme",
"registry:example",
"registry:style",
"registry:internal",
])

export const registryItemFileSchema = z.object({
path: z.string(),
content: z.string().optional(),
type: registryItemTypeSchema,
target: z.string().optional(),
})
export const registryItemFileSchema = z.discriminatedUnion("type", [
// Target is required for registry:file and registry:page
z.object({
path: z.string(),
content: z.string().optional(),
type: z.enum(["registry:file", "registry:page"]),
target: z.string(),
}),
z.object({
path: z.string(),
content: z.string().optional(),
type: registryItemTypeSchema.exclude(["registry:file", "registry:page"]),
target: z.string().optional(),
}),
])

export const registryItemTailwindSchema = z.object({
config: z
Expand Down
2 changes: 2 additions & 0 deletions packages/shadcn/src/utils/updaters/update-css-vars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export async function transformCssVars(
config: Config,
options: {
cleanupDefaultNextStyles?: boolean
} = {
cleanupDefaultNextStyles: false,
}
) {
options = {
Expand Down
146 changes: 121 additions & 25 deletions packages/shadcn/src/utils/updaters/update-files.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { existsSync, promises as fs } from "fs"
import path, { basename } from "path"
import {
getRegistryBaseColor,
getRegistryItemFileTargetPath,
} from "@/src/registry/api"
import { RegistryItem } from "@/src/registry/schema"
import { getRegistryBaseColor } from "@/src/registry/api"
import { RegistryItem, registryItemFileSchema } from "@/src/registry/schema"
import { Config } from "@/src/utils/get-config"
import { getProjectInfo } from "@/src/utils/get-project-info"
import { highlighter } from "@/src/utils/highlighter"
Expand All @@ -17,19 +14,7 @@ import { transformImport } from "@/src/utils/transformers/transform-import"
import { transformRsc } from "@/src/utils/transformers/transform-rsc"
import { transformTwPrefixes } from "@/src/utils/transformers/transform-tw-prefix"
import prompts from "prompts"

export function resolveTargetDir(
projectInfo: Awaited<ReturnType<typeof getProjectInfo>>,
config: Config,
target: string
) {
if (target.startsWith("~/")) {
return path.join(config.resolvedPaths.cwd, target.replace("~/", ""))
}
return projectInfo?.isSrcDir
? path.join(config.resolvedPaths.cwd, "src", target)
: path.join(config.resolvedPaths.cwd, target)
}
import { z } from "zod"

export async function updateFiles(
files: RegistryItem["files"],
Expand Down Expand Up @@ -74,14 +59,15 @@ export async function updateFiles(
continue
}

let targetDir = getRegistryItemFileTargetPath(file, config)
let filePath = resolveFilePath(file, config, {
isSrcDir: projectInfo?.isSrcDir,
commonRoot: findCommonRoot(
files.map((f) => f.path),
file.path
),
})
const fileName = basename(file.path)
let filePath = path.join(targetDir, fileName)

if (file.target) {
filePath = resolveTargetDir(projectInfo, config, file.target)
targetDir = path.dirname(filePath)
}
const targetDir = path.dirname(filePath)

if (!config.tsx) {
filePath = filePath.replace(/\.tsx?$/, (match) =>
Expand Down Expand Up @@ -210,3 +196,113 @@ export async function updateFiles(
filesSkipped,
}
}

export function resolveFilePath(
file: z.infer<typeof registryItemFileSchema>,
config: Config,
options: {
isSrcDir?: boolean
commonRoot?: string
}
) {
if (file.target) {
if (file.target.startsWith("~/")) {
return path.join(config.resolvedPaths.cwd, file.target.replace("~/", ""))
}

return options.isSrcDir
? path.join(
config.resolvedPaths.cwd,
"src",
file.target.replace("src/", "")
)
: path.join(config.resolvedPaths.cwd, file.target.replace("src/", ""))
}

const targetDir = resolveFileTargetDirectory(file, config)

const relativePath = resolveNestedFilePath(file.path, targetDir)
return path.join(targetDir, relativePath)
}

function resolveFileTargetDirectory(
file: z.infer<typeof registryItemFileSchema>,
config: Config
) {
if (file.type === "registry:ui") {
return config.resolvedPaths.ui
}

if (file.type === "registry:lib") {
return config.resolvedPaths.lib
}

if (file.type === "registry:block" || file.type === "registry:component") {
return config.resolvedPaths.components
}

if (file.type === "registry:hook") {
return config.resolvedPaths.hooks
}

return config.resolvedPaths.components
}

export function findCommonRoot(paths: string[], needle: string): string {
// Remove leading slashes for consistent handling
const normalizedPaths = paths.map((p) => p.replace(/^\//, ""))
const normalizedNeedle = needle.replace(/^\//, "")

// Get the directory path of the needle by removing the file name
const needleDir = normalizedNeedle.split("/").slice(0, -1).join("/")

// If needle is at root level, return empty string
if (!needleDir) {
return ""
}

// Split the needle directory into segments
const needleSegments = needleDir.split("/")

// Start from the full path and work backwards
for (let i = needleSegments.length; i > 0; i--) {
const testPath = needleSegments.slice(0, i).join("/")
// Check if this is a common root by verifying if any other paths start with it
const hasRelatedPaths = normalizedPaths.some(
(path) => path !== normalizedNeedle && path.startsWith(testPath + "/")
)
if (hasRelatedPaths) {
return "/" + testPath // Add leading slash back for the result
}
}

// If no common root found with other files, return the parent directory of the needle
return "/" + needleDir // Add leading slash back for the result
}

export function resolveNestedFilePath(
filePath: string,
targetDir: string
): string {
// Normalize paths by removing leading/trailing slashes
const normalizedFilePath = filePath.replace(/^\/|\/$/g, "")
const normalizedTargetDir = targetDir.replace(/^\/|\/$/g, "")

// Split paths into segments
const fileSegments = normalizedFilePath.split("/")
const targetSegments = normalizedTargetDir.split("/")

// Find the last matching segment from targetDir in filePath
const lastTargetSegment = targetSegments[targetSegments.length - 1]
const commonDirIndex = fileSegments.findIndex(
(segment) => segment === lastTargetSegment
)

if (commonDirIndex === -1) {
// Return just the filename if no common directory is found
return fileSegments[fileSegments.length - 1]
}

// Return everything after the common directory
return fileSegments.slice(commonDirIndex + 1).join("/")
}
Loading

0 comments on commit 6436242

Please sign in to comment.