This commit is contained in:
shadcn
2026-03-16 16:32:54 +04:00
parent c96b35b66e
commit 3f0fefd12b
4 changed files with 145 additions and 107 deletions

View File

@@ -21,6 +21,7 @@ import {
templates,
} from "@/src/templates/index"
import { addComponents } from "@/src/utils/add-components"
import { getInitAliasDefaults } from "@/src/utils/alias"
import { createProject } from "@/src/utils/create-project"
import { loadEnvFiles } from "@/src/utils/env-loader"
import * as ERRORS from "@/src/utils/errors"
@@ -905,63 +906,6 @@ async function promptForConfig(defaultConfig: Config | null = null) {
})
}
export function getInitAliasDefaults(
componentsAlias: string,
existingAliases?: Config["aliases"]
) {
const derivedLib =
existingAliases?.lib ?? deriveAliasFromComponents(componentsAlias, "lib")
return {
ui: existingAliases?.ui ?? deriveAliasFromComponents(componentsAlias, "ui"),
lib: derivedLib,
hooks:
existingAliases?.hooks ??
deriveAliasFromComponents(componentsAlias, "hooks"),
utils:
existingAliases?.utils ??
deriveAliasFromComponents(componentsAlias, "utils", derivedLib),
}
}
function deriveAliasFromComponents(
componentsAlias: string,
kind: "ui" | "lib" | "hooks" | "utils",
libAlias?: string
) {
const alias = componentsAlias || DEFAULT_COMPONENTS
if (kind === "ui") {
return `${alias}/ui`
}
if (kind === "utils") {
const resolvedLib = libAlias || replaceComponentsAliasTail(alias, "lib")
return resolvedLib ? `${resolvedLib}/utils` : DEFAULT_UTILS
}
return replaceComponentsAliasTail(alias, kind)
}
function replaceComponentsAliasTail(
alias: string,
kind: "lib" | "hooks"
) {
if (alias === "components") {
return kind
}
if (alias.endsWith("/components")) {
return `${alias.slice(0, -"/components".length)}/${kind}`
}
if (alias.endsWith("components") && !alias.includes("/")) {
return `${alias.slice(0, -"components".length)}${kind}`
}
return ""
}
export function shouldRunTemplatePostInit(
template:
| { postInit?: (options: { projectPath: string }) => Promise<void> }

View File

@@ -0,0 +1,78 @@
import { describe, expect, test } from "vitest"
import {
deriveAliasFromComponents,
getInitAliasDefaults,
} from "@/src/utils/alias"
describe("deriveAliasFromComponents", () => {
test("derives ui aliases from components", () => {
expect(deriveAliasFromComponents("@/components", "ui")).toBe(
"@/components/ui"
)
})
test("derives utils aliases from lib aliases", () => {
expect(deriveAliasFromComponents("#components", "utils")).toBe("#lib/utils")
expect(
deriveAliasFromComponents("#custom/components", "utils", "#custom/lib")
).toBe("#custom/lib/utils")
})
test("derives sibling lib and hooks aliases from components", () => {
expect(deriveAliasFromComponents("@/components", "lib")).toBe("@/lib")
expect(deriveAliasFromComponents("#custom/components", "hooks")).toBe(
"#custom/hooks"
)
})
test("returns an empty string when components alias has no sibling base", () => {
expect(deriveAliasFromComponents("#custom/ui", "lib")).toBe("")
})
})
describe("getInitAliasDefaults", () => {
test("derives standard aliases from components", () => {
expect(getInitAliasDefaults("@/components")).toEqual({
ui: "@/components/ui",
lib: "@/lib",
hooks: "@/hooks",
utils: "@/lib/utils",
})
})
test("derives package import aliases from #components", () => {
expect(getInitAliasDefaults("#components")).toEqual({
ui: "#components/ui",
lib: "#lib",
hooks: "#hooks",
utils: "#lib/utils",
})
})
test("derives sibling aliases for nested custom aliases", () => {
expect(getInitAliasDefaults("#custom/components")).toEqual({
ui: "#custom/components/ui",
lib: "#custom/lib",
hooks: "#custom/hooks",
utils: "#custom/lib/utils",
})
})
test("preserves existing aliases when components alias is unchanged", () => {
expect(
getInitAliasDefaults("#components", {
components: "#components",
ui: "#components/ui",
lib: "#lib",
hooks: "#hooks",
utils: "#lib/utils",
})
).toEqual({
ui: "#components/ui",
lib: "#lib",
hooks: "#hooks",
utils: "#lib/utils",
})
})
})

View File

@@ -0,0 +1,65 @@
import type { Config } from "@/src/utils/get-config"
import { DEFAULT_COMPONENTS, DEFAULT_UTILS } from "@/src/utils/get-config"
export function getInitAliasDefaults(
componentsAlias: string,
existingAliases?: Config["aliases"]
) {
// `lib` is the anchor for deriving `utils`, so reuse the existing value first
// when init is re-running against the same components alias.
const derivedLib =
existingAliases?.lib ?? deriveAliasFromComponents(componentsAlias, "lib")
return {
ui: existingAliases?.ui ?? deriveAliasFromComponents(componentsAlias, "ui"),
lib: derivedLib,
hooks:
existingAliases?.hooks ??
deriveAliasFromComponents(componentsAlias, "hooks"),
utils:
existingAliases?.utils ??
deriveAliasFromComponents(componentsAlias, "utils", derivedLib),
}
}
export function deriveAliasFromComponents(
componentsAlias: string,
kind: "ui" | "lib" | "hooks" | "utils",
libAlias?: string
) {
const alias = componentsAlias || DEFAULT_COMPONENTS
if (kind === "ui") {
return `${alias}/ui`
}
if (kind === "utils") {
// `utils` follows `lib`, not `components`, so derive or reuse the sibling
// lib alias before appending `/utils`.
const resolvedLib = libAlias || replaceComponentsAliasTail(alias, "lib")
return resolvedLib ? `${resolvedLib}/utils` : DEFAULT_UTILS
}
return replaceComponentsAliasTail(alias, kind)
}
function replaceComponentsAliasTail(
alias: string,
kind: "lib" | "hooks"
) {
// Handles the common `@/components` and `#custom/components` forms by
// swapping the trailing `components` segment for a sibling alias root.
if (alias === "components") {
return kind
}
if (alias.endsWith("/components")) {
return `${alias.slice(0, -"/components".length)}/${kind}`
}
if (alias.endsWith("components") && !alias.includes("/")) {
return `${alias.slice(0, -"components".length)}${kind}`
}
return ""
}

View File

@@ -1,55 +1,6 @@
import { describe, expect, test } from "vitest"
import {
getInitAliasDefaults,
shouldRunTemplatePostInit,
} from "../../src/commands/init"
describe("getInitAliasDefaults", () => {
test("derives standard aliases from components", () => {
expect(getInitAliasDefaults("@/components")).toEqual({
ui: "@/components/ui",
lib: "@/lib",
hooks: "@/hooks",
utils: "@/lib/utils",
})
})
test("derives package import aliases from #components", () => {
expect(getInitAliasDefaults("#components")).toEqual({
ui: "#components/ui",
lib: "#lib",
hooks: "#hooks",
utils: "#lib/utils",
})
})
test("derives sibling aliases for nested custom aliases", () => {
expect(getInitAliasDefaults("#custom/components")).toEqual({
ui: "#custom/components/ui",
lib: "#custom/lib",
hooks: "#custom/hooks",
utils: "#custom/lib/utils",
})
})
test("preserves existing aliases when components alias is unchanged", () => {
expect(
getInitAliasDefaults("#components", {
components: "#components",
ui: "#components/ui",
lib: "#lib",
hooks: "#hooks",
utils: "#lib/utils",
})
).toEqual({
ui: "#components/ui",
lib: "#lib",
hooks: "#hooks",
utils: "#lib/utils",
})
})
})
import { shouldRunTemplatePostInit } from "../../src/commands/init"
describe("shouldRunTemplatePostInit", () => {
test("does not run post-init for existing projects with an explicit template", () => {