mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-26 22:26:05 +00:00
feat: warn if in monorepo
This commit is contained in:
5
.changeset/shaggy-ways-check.md
Normal file
5
.changeset/shaggy-ways-check.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
warn if in monorepo and cwd not set
|
||||
@@ -8,6 +8,11 @@ import {
|
||||
} from "@/src/registry/api"
|
||||
import { registryIndexSchema } from "@/src/schema"
|
||||
import { Config, getConfig } from "@/src/utils/get-config"
|
||||
import {
|
||||
formatMonorepoMessage,
|
||||
getMonorepoTargets,
|
||||
isMonorepoRoot,
|
||||
} from "@/src/utils/get-monorepo-info"
|
||||
import { handleError } from "@/src/utils/handle-error"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
@@ -49,6 +54,15 @@ export const diff = new Command()
|
||||
|
||||
const config = await getConfig(cwd)
|
||||
if (!config) {
|
||||
// Check if we're in a monorepo root.
|
||||
if (await isMonorepoRoot(cwd)) {
|
||||
const targets = await getMonorepoTargets(cwd)
|
||||
if (targets.length > 0) {
|
||||
formatMonorepoMessage("diff [component]", targets)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
`Configuration is missing. Please run ${highlighter.success(
|
||||
`init`
|
||||
|
||||
@@ -26,6 +26,11 @@ import {
|
||||
resolveConfigPaths,
|
||||
type Config,
|
||||
} from "@/src/utils/get-config"
|
||||
import {
|
||||
formatMonorepoMessage,
|
||||
getMonorepoTargets,
|
||||
isMonorepoRoot,
|
||||
} from "@/src/utils/get-monorepo-info"
|
||||
import {
|
||||
getProjectComponents,
|
||||
getProjectConfig,
|
||||
@@ -168,6 +173,18 @@ export const init = new Command()
|
||||
path.resolve(cwd, "components.json")
|
||||
)
|
||||
|
||||
// Check if we're in a monorepo root before proceeding.
|
||||
if (!hasExistingConfig && (await isMonorepoRoot(cwd))) {
|
||||
const projectInfo = await getProjectInfo(cwd)
|
||||
if (!projectInfo || projectInfo.framework.name === "manual") {
|
||||
const targets = await getMonorepoTargets(cwd)
|
||||
if (targets.length > 0) {
|
||||
formatMonorepoMessage("init", targets)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasExistingConfig && !options.force) {
|
||||
const { overwrite } = await prompts({
|
||||
type: "confirm",
|
||||
@@ -286,6 +303,7 @@ export const init = new Command()
|
||||
const result = await promptForPreset({
|
||||
rtl: options.rtl ?? false,
|
||||
template: options.template,
|
||||
base: options.base,
|
||||
})
|
||||
components = [result.url, ...components]
|
||||
presetBase = result.base
|
||||
|
||||
@@ -2,6 +2,11 @@ import path from "path"
|
||||
import { addOptionsSchema } from "@/src/commands/add"
|
||||
import * as ERRORS from "@/src/utils/errors"
|
||||
import { getConfig } from "@/src/utils/get-config"
|
||||
import {
|
||||
formatMonorepoMessage,
|
||||
getMonorepoTargets,
|
||||
isMonorepoRoot,
|
||||
} from "@/src/utils/get-monorepo-info"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
import fs from "fs-extra"
|
||||
@@ -25,6 +30,15 @@ export async function preFlightAdd(options: z.infer<typeof addOptionsSchema>) {
|
||||
|
||||
// Check for existing components.json file.
|
||||
if (!fs.existsSync(path.resolve(options.cwd, "components.json"))) {
|
||||
// Check if we're in a monorepo root.
|
||||
if (await isMonorepoRoot(options.cwd)) {
|
||||
const targets = await getMonorepoTargets(options.cwd)
|
||||
if (targets.length > 0) {
|
||||
formatMonorepoMessage("add [component]", targets)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
errors[ERRORS.MISSING_CONFIG] = true
|
||||
return {
|
||||
errors,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import path from "path"
|
||||
import { initOptionsSchema } from "@/src/commands/init"
|
||||
import * as ERRORS from "@/src/utils/errors"
|
||||
import {
|
||||
formatMonorepoMessage,
|
||||
getMonorepoTargets,
|
||||
isMonorepoRoot,
|
||||
} from "@/src/utils/get-monorepo-info"
|
||||
import { getProjectInfo } from "@/src/utils/get-project-info"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
@@ -63,6 +68,16 @@ export async function preFlightInit(
|
||||
if (!projectInfo || projectInfo?.framework.name === "manual") {
|
||||
errors[ERRORS.UNSUPPORTED_FRAMEWORK] = true
|
||||
frameworkSpinner?.fail()
|
||||
|
||||
// Check if we're in a monorepo root.
|
||||
if (await isMonorepoRoot(options.cwd)) {
|
||||
const targets = await getMonorepoTargets(options.cwd)
|
||||
if (targets.length > 0) {
|
||||
formatMonorepoMessage("init", targets)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
logger.break()
|
||||
if (projectInfo?.framework.links.installation) {
|
||||
logger.error(
|
||||
|
||||
289
packages/shadcn/src/utils/get-monorepo-info.test.ts
Normal file
289
packages/shadcn/src/utils/get-monorepo-info.test.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
import path from "path"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
import fs from "fs-extra"
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import {
|
||||
formatMonorepoMessage,
|
||||
getMonorepoTargets,
|
||||
isMonorepoRoot,
|
||||
} from "./get-monorepo-info"
|
||||
|
||||
let tmpDir: string
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDir = path.join(
|
||||
await fs.realpath(require("os").tmpdir()),
|
||||
`shadcn-monorepo-test-${Date.now()}`
|
||||
)
|
||||
await fs.ensureDir(tmpDir)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await fs.remove(tmpDir)
|
||||
})
|
||||
|
||||
describe("isMonorepoRoot", () => {
|
||||
it("should detect pnpm-workspace.yaml", async () => {
|
||||
await fs.writeFile(
|
||||
path.join(tmpDir, "pnpm-workspace.yaml"),
|
||||
"packages:\n - apps/*\n"
|
||||
)
|
||||
expect(await isMonorepoRoot(tmpDir)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect package.json with workspaces array", async () => {
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), {
|
||||
name: "root",
|
||||
workspaces: ["apps/*", "packages/*"],
|
||||
})
|
||||
expect(await isMonorepoRoot(tmpDir)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect package.json with workspaces.packages", async () => {
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), {
|
||||
name: "root",
|
||||
workspaces: { packages: ["apps/*"] },
|
||||
})
|
||||
expect(await isMonorepoRoot(tmpDir)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect lerna.json", async () => {
|
||||
await fs.writeJson(path.join(tmpDir, "lerna.json"), { version: "0.0.0" })
|
||||
expect(await isMonorepoRoot(tmpDir)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect nx.json", async () => {
|
||||
await fs.writeJson(path.join(tmpDir, "nx.json"), {})
|
||||
expect(await isMonorepoRoot(tmpDir)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false for a regular project", async () => {
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), { name: "my-app" })
|
||||
expect(await isMonorepoRoot(tmpDir)).toBe(false)
|
||||
})
|
||||
|
||||
it("should return false for an empty directory", async () => {
|
||||
expect(await isMonorepoRoot(tmpDir)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getMonorepoTargets", () => {
|
||||
it("should find targets from pnpm-workspace.yaml", async () => {
|
||||
// Set up monorepo structure.
|
||||
await fs.writeFile(
|
||||
path.join(tmpDir, "pnpm-workspace.yaml"),
|
||||
"packages:\n - 'apps/*'\n"
|
||||
)
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), { name: "root" })
|
||||
|
||||
// Create an app with a Next.js config.
|
||||
const webDir = path.join(tmpDir, "apps", "web")
|
||||
await fs.ensureDir(webDir)
|
||||
await fs.writeJson(path.join(webDir, "package.json"), { name: "web" })
|
||||
await fs.writeFile(
|
||||
path.join(webDir, "next.config.mjs"),
|
||||
"export default {}"
|
||||
)
|
||||
|
||||
const targets = await getMonorepoTargets(tmpDir)
|
||||
expect(targets).toEqual([{ name: "apps/web", hasConfig: false }])
|
||||
})
|
||||
|
||||
it("should find targets from package.json workspaces", async () => {
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), {
|
||||
name: "root",
|
||||
workspaces: ["apps/*"],
|
||||
})
|
||||
|
||||
const webDir = path.join(tmpDir, "apps", "web")
|
||||
await fs.ensureDir(webDir)
|
||||
await fs.writeJson(path.join(webDir, "package.json"), { name: "web" })
|
||||
await fs.writeFile(path.join(webDir, "vite.config.ts"), "export default {}")
|
||||
|
||||
const targets = await getMonorepoTargets(tmpDir)
|
||||
expect(targets).toEqual([{ name: "apps/web", hasConfig: false }])
|
||||
})
|
||||
|
||||
it("should set hasConfig when components.json exists", async () => {
|
||||
await fs.writeFile(
|
||||
path.join(tmpDir, "pnpm-workspace.yaml"),
|
||||
"packages:\n - apps/*\n"
|
||||
)
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), { name: "root" })
|
||||
|
||||
const webDir = path.join(tmpDir, "apps", "web")
|
||||
await fs.ensureDir(webDir)
|
||||
await fs.writeJson(path.join(webDir, "package.json"), { name: "web" })
|
||||
await fs.writeFile(
|
||||
path.join(webDir, "next.config.mjs"),
|
||||
"export default {}"
|
||||
)
|
||||
await fs.writeJson(path.join(webDir, "components.json"), {})
|
||||
|
||||
const targets = await getMonorepoTargets(tmpDir)
|
||||
expect(targets).toEqual([{ name: "apps/web", hasConfig: true }])
|
||||
})
|
||||
|
||||
it("should find multiple targets", async () => {
|
||||
await fs.writeFile(
|
||||
path.join(tmpDir, "pnpm-workspace.yaml"),
|
||||
"packages:\n - apps/*\n"
|
||||
)
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), { name: "root" })
|
||||
|
||||
// apps/web with Next.js.
|
||||
const webDir = path.join(tmpDir, "apps", "web")
|
||||
await fs.ensureDir(webDir)
|
||||
await fs.writeJson(path.join(webDir, "package.json"), { name: "web" })
|
||||
await fs.writeFile(
|
||||
path.join(webDir, "next.config.mjs"),
|
||||
"export default {}"
|
||||
)
|
||||
|
||||
// apps/docs with Vite.
|
||||
const docsDir = path.join(tmpDir, "apps", "docs")
|
||||
await fs.ensureDir(docsDir)
|
||||
await fs.writeJson(path.join(docsDir, "package.json"), { name: "docs" })
|
||||
await fs.writeFile(
|
||||
path.join(docsDir, "vite.config.ts"),
|
||||
"export default {}"
|
||||
)
|
||||
|
||||
const targets = await getMonorepoTargets(tmpDir)
|
||||
expect(targets).toHaveLength(2)
|
||||
expect(targets.map((t) => t.name).sort()).toEqual(["apps/docs", "apps/web"])
|
||||
})
|
||||
|
||||
it("should skip directories without package.json", async () => {
|
||||
await fs.writeFile(
|
||||
path.join(tmpDir, "pnpm-workspace.yaml"),
|
||||
"packages:\n - apps/*\n"
|
||||
)
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), { name: "root" })
|
||||
|
||||
// Directory without package.json.
|
||||
const libDir = path.join(tmpDir, "apps", "lib")
|
||||
await fs.ensureDir(libDir)
|
||||
await fs.writeFile(
|
||||
path.join(libDir, "next.config.mjs"),
|
||||
"export default {}"
|
||||
)
|
||||
|
||||
const targets = await getMonorepoTargets(tmpDir)
|
||||
expect(targets).toEqual([])
|
||||
})
|
||||
|
||||
it("should skip directories without framework config or components.json", async () => {
|
||||
await fs.writeFile(
|
||||
path.join(tmpDir, "pnpm-workspace.yaml"),
|
||||
"packages:\n - packages/*\n"
|
||||
)
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), { name: "root" })
|
||||
|
||||
// A utility package with no framework config.
|
||||
const utilsDir = path.join(tmpDir, "packages", "utils")
|
||||
await fs.ensureDir(utilsDir)
|
||||
await fs.writeJson(path.join(utilsDir, "package.json"), { name: "utils" })
|
||||
|
||||
const targets = await getMonorepoTargets(tmpDir)
|
||||
expect(targets).toEqual([])
|
||||
})
|
||||
|
||||
it("should return empty for no workspace patterns", async () => {
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), { name: "root" })
|
||||
const targets = await getMonorepoTargets(tmpDir)
|
||||
expect(targets).toEqual([])
|
||||
})
|
||||
|
||||
it("should detect astro, remix, and svelte configs", async () => {
|
||||
await fs.writeFile(
|
||||
path.join(tmpDir, "pnpm-workspace.yaml"),
|
||||
"packages:\n - apps/*\n"
|
||||
)
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), { name: "root" })
|
||||
|
||||
const astroDir = path.join(tmpDir, "apps", "astro-app")
|
||||
await fs.ensureDir(astroDir)
|
||||
await fs.writeJson(path.join(astroDir, "package.json"), {
|
||||
name: "astro-app",
|
||||
})
|
||||
await fs.writeFile(
|
||||
path.join(astroDir, "astro.config.mjs"),
|
||||
"export default {}"
|
||||
)
|
||||
|
||||
const targets = await getMonorepoTargets(tmpDir)
|
||||
expect(targets).toEqual([{ name: "apps/astro-app", hasConfig: false }])
|
||||
})
|
||||
|
||||
it("should deduplicate patterns from both pnpm-workspace.yaml and package.json", async () => {
|
||||
await fs.writeFile(
|
||||
path.join(tmpDir, "pnpm-workspace.yaml"),
|
||||
"packages:\n - apps/*\n"
|
||||
)
|
||||
await fs.writeJson(path.join(tmpDir, "package.json"), {
|
||||
name: "root",
|
||||
workspaces: ["apps/*"],
|
||||
})
|
||||
|
||||
const webDir = path.join(tmpDir, "apps", "web")
|
||||
await fs.ensureDir(webDir)
|
||||
await fs.writeJson(path.join(webDir, "package.json"), { name: "web" })
|
||||
await fs.writeFile(
|
||||
path.join(webDir, "next.config.mjs"),
|
||||
"export default {}"
|
||||
)
|
||||
|
||||
const targets = await getMonorepoTargets(tmpDir)
|
||||
// Should not duplicate the target.
|
||||
expect(targets).toEqual([{ name: "apps/web", hasConfig: false }])
|
||||
})
|
||||
})
|
||||
|
||||
describe("formatMonorepoMessage", () => {
|
||||
it("should log the monorepo message with targets", () => {
|
||||
const logSpy = vi.spyOn(logger, "log")
|
||||
const breakSpy = vi.spyOn(logger, "break")
|
||||
|
||||
formatMonorepoMessage("init", [
|
||||
{ name: "apps/web", hasConfig: false },
|
||||
{ name: "apps/docs", hasConfig: true },
|
||||
])
|
||||
|
||||
expect(breakSpy).toHaveBeenCalled()
|
||||
const allLogCalls = logSpy.mock.calls.map((c) => c[0])
|
||||
|
||||
// Should mention monorepo root.
|
||||
expect(allLogCalls.some((msg) => msg.includes("monorepo root"))).toBe(true)
|
||||
// Should mention -c flag.
|
||||
expect(allLogCalls.some((msg) => msg.includes("-c"))).toBe(true)
|
||||
// Should list both targets.
|
||||
expect(
|
||||
allLogCalls.some((msg) => msg.includes("shadcn init -c apps/web"))
|
||||
).toBe(true)
|
||||
expect(
|
||||
allLogCalls.some((msg) => msg.includes("shadcn init -c apps/docs"))
|
||||
).toBe(true)
|
||||
|
||||
logSpy.mockRestore()
|
||||
breakSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("should use the correct command name", () => {
|
||||
const logSpy = vi.spyOn(logger, "log")
|
||||
|
||||
formatMonorepoMessage("add [component]", [
|
||||
{ name: "apps/web", hasConfig: false },
|
||||
])
|
||||
|
||||
const allLogCalls = logSpy.mock.calls.map((c) => c[0])
|
||||
expect(
|
||||
allLogCalls.some((msg) =>
|
||||
msg.includes("shadcn add [component] -c apps/web")
|
||||
)
|
||||
).toBe(true)
|
||||
|
||||
logSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
157
packages/shadcn/src/utils/get-monorepo-info.ts
Normal file
157
packages/shadcn/src/utils/get-monorepo-info.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import path from "path"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
import fg from "fast-glob"
|
||||
import fs from "fs-extra"
|
||||
|
||||
const FRAMEWORK_CONFIG_FILES = [
|
||||
"next.config.*",
|
||||
"vite.config.*",
|
||||
"astro.config.*",
|
||||
"remix.config.*",
|
||||
"nuxt.config.*",
|
||||
"svelte.config.*",
|
||||
"gatsby-config.*",
|
||||
"angular.json",
|
||||
]
|
||||
|
||||
// Checks for workspace signals at the given directory.
|
||||
export async function isMonorepoRoot(cwd: string) {
|
||||
// pnpm workspaces.
|
||||
if (fs.existsSync(path.resolve(cwd, "pnpm-workspace.yaml"))) {
|
||||
return true
|
||||
}
|
||||
|
||||
// npm/yarn workspaces.
|
||||
const packageJsonPath = path.resolve(cwd, "package.json")
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
try {
|
||||
const packageJson = await fs.readJson(packageJsonPath)
|
||||
if (packageJson.workspaces) {
|
||||
return true
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors.
|
||||
}
|
||||
}
|
||||
|
||||
// Lerna.
|
||||
if (fs.existsSync(path.resolve(cwd, "lerna.json"))) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Nx.
|
||||
if (fs.existsSync(path.resolve(cwd, "nx.json"))) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Finds app directories in a monorepo that contain framework configs or components.json.
|
||||
export async function getMonorepoTargets(cwd: string) {
|
||||
const patterns = await getWorkspacePatterns(cwd)
|
||||
|
||||
if (!patterns.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Resolve patterns to directories.
|
||||
const dirs = await fg(patterns, {
|
||||
cwd,
|
||||
onlyDirectories: true,
|
||||
ignore: ["**/node_modules/**"],
|
||||
})
|
||||
|
||||
const targets: { name: string; hasConfig: boolean }[] = []
|
||||
|
||||
for (const dir of dirs) {
|
||||
const fullPath = path.resolve(cwd, dir)
|
||||
|
||||
// Check if it has a package.json (it's an actual workspace).
|
||||
if (!fs.existsSync(path.resolve(fullPath, "package.json"))) {
|
||||
continue
|
||||
}
|
||||
|
||||
const hasComponentsJson = fs.existsSync(
|
||||
path.resolve(fullPath, "components.json")
|
||||
)
|
||||
|
||||
// Check for framework config files.
|
||||
const hasFrameworkConfig = FRAMEWORK_CONFIG_FILES.some((pattern) => {
|
||||
const matches = fg.sync(pattern, {
|
||||
cwd: fullPath,
|
||||
dot: true,
|
||||
})
|
||||
return matches.length > 0
|
||||
})
|
||||
|
||||
if (hasComponentsJson || hasFrameworkConfig) {
|
||||
targets.push({
|
||||
name: dir,
|
||||
hasConfig: hasComponentsJson,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return targets
|
||||
}
|
||||
|
||||
// Formats and logs the monorepo detection message.
|
||||
export function formatMonorepoMessage(
|
||||
command: string,
|
||||
targets: { name: string; hasConfig: boolean }[]
|
||||
) {
|
||||
logger.break()
|
||||
logger.log(
|
||||
`It looks like you are running ${highlighter.info(
|
||||
command
|
||||
)} from a monorepo root.`
|
||||
)
|
||||
logger.log(
|
||||
`To use shadcn in a specific workspace, use the ${highlighter.info(
|
||||
"-c"
|
||||
)} flag:`
|
||||
)
|
||||
logger.break()
|
||||
|
||||
for (const target of targets) {
|
||||
logger.log(` shadcn ${command} -c ${target.name}`)
|
||||
}
|
||||
|
||||
logger.break()
|
||||
}
|
||||
|
||||
async function getWorkspacePatterns(cwd: string) {
|
||||
const patterns: string[] = []
|
||||
|
||||
// Read pnpm-workspace.yaml.
|
||||
const pnpmWorkspacePath = path.resolve(cwd, "pnpm-workspace.yaml")
|
||||
if (fs.existsSync(pnpmWorkspacePath)) {
|
||||
const content = await fs.readFile(pnpmWorkspacePath, "utf8")
|
||||
// Simple regex parse to extract patterns from packages list.
|
||||
const matches = content.matchAll(/^\s*-\s*["']?([^"'\n#]+)["']?\s*$/gm)
|
||||
for (const match of matches) {
|
||||
patterns.push(match[1].trim())
|
||||
}
|
||||
}
|
||||
|
||||
// Read package.json workspaces.
|
||||
const packageJsonPath = path.resolve(cwd, "package.json")
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
try {
|
||||
const packageJson = await fs.readJson(packageJsonPath)
|
||||
const workspaces = Array.isArray(packageJson.workspaces)
|
||||
? packageJson.workspaces
|
||||
: packageJson.workspaces?.packages
|
||||
if (Array.isArray(workspaces)) {
|
||||
// Filter out negation patterns.
|
||||
patterns.push(...workspaces.filter((w: string) => !w.startsWith("!")))
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors.
|
||||
}
|
||||
}
|
||||
|
||||
return [...new Set(patterns)]
|
||||
}
|
||||
@@ -99,8 +99,11 @@ export function resolveInitUrl(
|
||||
export async function promptForPreset(options: {
|
||||
rtl: boolean
|
||||
template?: string
|
||||
base?: string
|
||||
}) {
|
||||
const presets = Object.values(DEFAULT_PRESETS)
|
||||
const presets = Object.values(DEFAULT_PRESETS).filter(
|
||||
(preset) => !options.base || preset.base === options.base
|
||||
)
|
||||
|
||||
const { selectedPreset } = await prompts({
|
||||
type: "select",
|
||||
|
||||
Reference in New Issue
Block a user