Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/friendly-suns-compete.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions apps/web/src/content/docs/docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>

Install a component/block from configured registries (see components.json → registries). Writes files under your aliases.
Expand Down
2 changes: 2 additions & 0 deletions packages/bejamas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
188 changes: 144 additions & 44 deletions packages/bejamas/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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(),
Expand All @@ -57,26 +56,127 @@ 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,
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,
};
}

return {
cmd: "npx",
args: ["-y", "shadcn@latest", ...SHADCN_INIT_ARGS],
};
}

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;
}

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<string, unknown>;
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<string, unknown>;
} 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<string, unknown>),
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")
Expand All @@ -87,7 +187,7 @@ export const init = new Command()
)
.option(
"-b, --base-color <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)
Expand Down Expand Up @@ -127,7 +227,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);
Expand All @@ -141,9 +245,6 @@ export async function runInit(
options.isNewProject = true;
newProjectTemplate = template;
}
projectInfo = preflight.projectInfo;
} else {
projectInfo = await getProjectInfo(options.cwd);
}

if (newProjectTemplate) {
Expand Down Expand Up @@ -178,26 +279,25 @@ 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 localShadcnVersion = hasLocalShadcn
? await getLocalShadcnVersion(options.cwd)
: null;
const invocation = buildShadcnInitInvocation(
localShadcnPath,
hasLocalShadcn,
localShadcnVersion,
);

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);
}