From b608ac19b71b1cce6111118051fc5234f494d3a1 Mon Sep 17 00:00:00 2001 From: thom krupa Date: Wed, 18 Mar 2026 18:43:51 +0100 Subject: [PATCH 1/2] fix(bejamas): remove option --base-color --- .changeset/friendly-suns-compete.md | 5 + apps/web/src/content/docs/docs/cli.md | 2 + packages/bejamas/README.md | 2 + packages/bejamas/src/commands/init.ts | 157 ++++++++++++++++++-------- 4 files changed, 122 insertions(+), 44 deletions(-) create mode 100644 .changeset/friendly-suns-compete.md diff --git a/.changeset/friendly-suns-compete.md b/.changeset/friendly-suns-compete.md new file mode 100644 index 00000000..7ba15259 --- /dev/null +++ b/.changeset/friendly-suns-compete.md @@ -0,0 +1,5 @@ +--- +"bejamas": patch +--- + +Fix `bejamas init` against current `shadcn@latest` by removing the obsolete upstream `--base-color` flag, warning on deprecated `bejamas --base-color`, and normalizing generated `components.json` back to Bejamas's default `neutral` base color. diff --git a/apps/web/src/content/docs/docs/cli.md b/apps/web/src/content/docs/docs/cli.md index 8e23665e..8922bc3f 100644 --- a/apps/web/src/content/docs/docs/cli.md +++ b/apps/web/src/content/docs/docs/cli.md @@ -42,6 +42,8 @@ Base tokens & CSS variables components.json (shadcn schema) +Compatibility note: `--base-color` is deprecated and ignored on the current shadcn v4-backed init flow. To use a different base color, update `tailwind.baseColor` in `components.json` after init. + ### add Install a component/block from configured registries (see components.json → registries). Writes files under your aliases. diff --git a/packages/bejamas/README.md b/packages/bejamas/README.md index ea233291..f6deb448 100644 --- a/packages/bejamas/README.md +++ b/packages/bejamas/README.md @@ -8,6 +8,8 @@ Use the `init` command to initialize dependencies for a new project. The `init` command installs dependencies, adds the `cn` util, and configures CSS variables for the project. +`--base-color` is deprecated and ignored on current shadcn-backed init flows. If you want a different base color, update `tailwind.baseColor` in `components.json` after initialization. + ```bash npx bejamas init ``` diff --git a/packages/bejamas/src/commands/init.ts b/packages/bejamas/src/commands/init.ts index 372510e2..bee77ed9 100644 --- a/packages/bejamas/src/commands/init.ts +++ b/packages/bejamas/src/commands/init.ts @@ -2,14 +2,11 @@ import { promises as fs } from "fs"; import path from "path"; import { preFlightInit } from "@/src/preflights/preflight-init"; -import { BASE_COLORS, BUILTIN_REGISTRIES } from "@/src/registry/constants"; import { clearRegistryContext } from "@/src/registry/context"; import { TEMPLATES, createProject } from "@/src/utils/create-project"; import * as ERRORS from "@/src/utils/errors"; -import { getConfig, createConfig, type Config } from "@/src/utils/get-config"; -import { getProjectConfig, getProjectInfo } from "@/src/utils/get-project-info"; -import { getPackageRunner } from "@/src/utils/get-package-manager"; +import { getConfig } from "@/src/utils/get-config"; import { handleError } from "@/src/utils/handle-error"; import { highlighter } from "@/src/utils/highlighter"; import { logger } from "@/src/utils/logger"; @@ -32,6 +29,8 @@ import { z } from "zod"; // Default fallback registry endpoint for shadcn (expects /r) const DEFAULT_REGISTRY_URL = "https://ui.bejamas.com/r"; +export const DEFAULT_COMPONENTS_BASE_COLOR = "neutral"; +const SHADCN_INIT_ARGS = ["init"] as const; export const initOptionsSchema = z.object({ cwd: z.string(), @@ -57,26 +56,100 @@ export const initOptionsSchema = z.object({ message: "Invalid template. Please use 'next' or 'next-monorepo'.", }, ), - baseColor: z - .string() - .optional() - .refine( - (val) => { - if (val) { - return BASE_COLORS.find((color) => color.name === val); - } - - return true; - }, - { - message: `Invalid base color. Please use '${BASE_COLORS.map( - (color) => color.name, - ).join("', '")}'`, - }, - ), + baseColor: z.string().optional(), baseStyle: z.boolean(), }); +export function buildShadcnInitInvocation( + localShadcnPath: string, + hasLocalShadcn: boolean, +) { + if (hasLocalShadcn) { + return { + cmd: localShadcnPath, + args: [...SHADCN_INIT_ARGS], + }; + } + + return { + cmd: "npx", + args: ["-y", "shadcn@latest", ...SHADCN_INIT_ARGS], + }; +} + +export function getDeprecatedBaseColorWarning(baseColor?: string) { + if (!baseColor) { + return null; + } + + return [ + "The --base-color option is deprecated and ignored.", + "Bejamas now aligns with shadcn CLI v4.", + `Edit ${highlighter.info("components.json")} to change`, + `${highlighter.info("tailwind.baseColor")} after init.`, + ].join(" "); +} + +export async function normalizeComponentsBaseColor( + cwd: string, + baseColor = DEFAULT_COMPONENTS_BASE_COLOR, +) { + const filePath = path.resolve(cwd, "components.json"); + + let originalContents: string; + try { + originalContents = await fs.readFile(filePath, "utf8"); + } catch { + throw new Error( + `Failed to read ${highlighter.info( + filePath, + )} after shadcn init. Make sure the command completed successfully and created a valid components.json file.`, + ); + } + + let parsedConfig: Record; + try { + const parsed = JSON.parse(originalContents); + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new Error("Expected a JSON object."); + } + parsedConfig = parsed as Record; + } catch { + throw new Error( + `Failed to parse ${highlighter.info( + filePath, + )} after shadcn init. Expected a valid JSON object with a tailwind configuration.`, + ); + } + + const tailwind = parsedConfig.tailwind; + if (!tailwind || typeof tailwind !== "object" || Array.isArray(tailwind)) { + throw new Error( + `Invalid ${highlighter.info( + filePath, + )} generated by shadcn init. Expected a ${highlighter.info( + "tailwind", + )} object so Bejamas can normalize ${highlighter.info("baseColor")}.`, + ); + } + + const normalizedConfig = { + ...parsedConfig, + tailwind: { + ...(tailwind as Record), + baseColor, + }, + }; + const normalizedContents = `${JSON.stringify(normalizedConfig, null, 2)}\n`; + + if (normalizedContents === originalContents) { + return false; + } + + await fs.writeFile(filePath, normalizedContents, "utf8"); + return true; +} + export const init = new Command() .name("init") .description("initialize your project and install dependencies") @@ -87,7 +160,7 @@ export const init = new Command() ) .option( "-b, --base-color ", - "the base color to use. (neutral, gray, zinc, stone, slate)", + "deprecated: accepted for compatibility but ignored. Edit components.json to change tailwind.baseColor.", undefined, ) .option("-y, --yes", "skip confirmation prompt.", true) @@ -127,7 +200,11 @@ export async function runInit( skipPreflight?: boolean; }, ) { - let projectInfo; + const baseColorWarning = getDeprecatedBaseColorWarning(options.baseColor); + if (baseColorWarning && !options.silent) { + logger.warn(baseColorWarning); + } + let newProjectTemplate; if (!options.skipPreflight) { const preflight = await preFlightInit(options); @@ -141,9 +218,6 @@ export async function runInit( options.isNewProject = true; newProjectTemplate = template; } - projectInfo = preflight.projectInfo; - } else { - projectInfo = await getProjectInfo(options.cwd); } if (newProjectTemplate) { @@ -178,26 +252,21 @@ export async function runInit( ...process.env, REGISTRY_URL: process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL, }; - if (await fsExtra.pathExists(localShadcnPath)) { - await execa(localShadcnPath, ["init", "--base-color", "neutral"], { - stdio: "inherit", - cwd: options.cwd, - env, - }); - } else { - // Follow user's runner preference (npx, bunx, pnpm dlx) - await execa( - "npx", - ["-y", "shadcn@latest", "init", "--base-color", "neutral"], - { - stdio: "inherit", - cwd: options.cwd, - env, - }, - ); - } + const hasLocalShadcn = await fsExtra.pathExists(localShadcnPath); + const invocation = buildShadcnInitInvocation( + localShadcnPath, + hasLocalShadcn, + ); + + await execa(invocation.cmd, invocation.args, { + stdio: "inherit", + cwd: options.cwd, + env, + }); } catch (err) { // shadcn already printed the detailed error to stdio, avoid double-reporting process.exit(1); } + + await normalizeComponentsBaseColor(options.cwd); } From b784bfa21ce0e7a8fa4eefd325463355a36771fd Mon Sep 17 00:00:00 2001 From: thom krupa Date: Wed, 18 Mar 2026 20:09:54 +0100 Subject: [PATCH 2/2] fix(bejamas): support shadcn v4 init and keep local v3 init non-interactive --- packages/bejamas/src/commands/init.ts | 33 ++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/bejamas/src/commands/init.ts b/packages/bejamas/src/commands/init.ts index bee77ed9..52e8489d 100644 --- a/packages/bejamas/src/commands/init.ts +++ b/packages/bejamas/src/commands/init.ts @@ -63,11 +63,17 @@ export const initOptionsSchema = z.object({ export function buildShadcnInitInvocation( localShadcnPath: string, hasLocalShadcn: boolean, + localShadcnVersion?: string | null, ) { if (hasLocalShadcn) { + const args = [...SHADCN_INIT_ARGS]; + if (usesLegacyBaseColorFlag(localShadcnVersion)) { + args.push("--base-color", DEFAULT_COMPONENTS_BASE_COLOR); + } + return { cmd: localShadcnPath, - args: [...SHADCN_INIT_ARGS], + args, }; } @@ -77,6 +83,27 @@ export function buildShadcnInitInvocation( }; } +export function usesLegacyBaseColorFlag(version?: string | null) { + if (!version) { + return false; + } + + const major = Number.parseInt(version.split(".")[0] ?? "", 10); + return Number.isFinite(major) && major > 0 && major < 4; +} + +export async function getLocalShadcnVersion(cwd: string) { + const filePath = path.resolve(cwd, "node_modules", "shadcn", "package.json"); + + try { + const contents = await fs.readFile(filePath, "utf8"); + const parsed = JSON.parse(contents) as { version?: unknown }; + return typeof parsed.version === "string" ? parsed.version : null; + } catch { + return null; + } +} + export function getDeprecatedBaseColorWarning(baseColor?: string) { if (!baseColor) { return null; @@ -253,9 +280,13 @@ export async function runInit( REGISTRY_URL: process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL, }; const hasLocalShadcn = await fsExtra.pathExists(localShadcnPath); + const localShadcnVersion = hasLocalShadcn + ? await getLocalShadcnVersion(options.cwd) + : null; const invocation = buildShadcnInitInvocation( localShadcnPath, hasLocalShadcn, + localShadcnVersion, ); await execa(invocation.cmd, invocation.args, {