Skip to content

Commit 2ac3099

Browse files
authored
Merge pull request #100 from bejamas/fix/shadcn-v3
fix(bejamas): pin shadcn CLI to v3.8.5 for init and add
2 parents 78d155f + a43a019 commit 2ac3099

9 files changed

Lines changed: 81 additions & 177 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"bejamas": patch
3+
---
4+
5+
Pin Bejamas-managed `shadcn` calls back to `shadcn@3.8.5` for compatibility with the current Bejamas registry/init flow. This restores the legacy `init --base-color` behavior and stops routing `init` and `add` through `shadcn@latest`.

apps/web/src/content/docs/docs/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Base tokens & CSS variables
4242

4343
components.json (shadcn schema)
4444

45-
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.
45+
Compatibility note: Bejamas currently pins `shadcn` v3.8.5 internally for `init` and `add`. This keeps the pre-v4 init/add behavior, including `init --base-color`.
4646

4747
### add <name>
4848

bun.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/bejamas/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Use the `init` command to initialize dependencies for a new project.
88

99
The `init` command installs dependencies, adds the `cn` util, and configures CSS variables for the project.
1010

11-
`--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.
11+
Bejamas currently pins `shadcn` v3.8.5 internally for `init` and `add` compatibility. The legacy `--base-color` flow remains supported on `init`.
1212

1313
```bash
1414
npx bejamas init

packages/bejamas/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"kleur": "^4.1.5",
4949
"ora": "^9.0.0",
5050
"prompts": "^2.4.2",
51-
"shadcn": "^3.3.1",
51+
"shadcn": "3.8.5",
5252
"ts-morph": "^27.0.0",
5353
"tsconfig-paths": "^4.2.0",
5454
"zod": "^4.1.9"

packages/bejamas/src/commands/add.ts

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import prompts from "prompts";
55
import { logger } from "@/src/utils/logger";
66
import { spinner } from "@/src/utils/spinner";
77
import { highlighter } from "@/src/utils/highlighter";
8-
import { getPackageRunner } from "@/src/utils/get-package-manager";
98
import { fixAstroImports } from "@/src/utils/astro-imports";
109
import { getConfig, getWorkspaceConfig } from "@/src/utils/get-config";
10+
import { buildPinnedShadcnInvocation } from "@/src/utils/shadcn-cli";
1111
import {
1212
reorganizeComponents,
1313
fetchRegistryItem,
@@ -336,40 +336,15 @@ async function addComponents(
336336
isSilent: boolean,
337337
subfolderMapResult: SubfolderMapResult,
338338
): Promise<ParsedOutput> {
339-
const runner = await getPackageRunner(process.cwd());
340339
const env = {
341340
...process.env,
342341
REGISTRY_URL: process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL,
343342
};
344-
// Always pass --yes for non-interactive mode (skips "Add components?" confirmation)
345-
// Note: we don't pass --overwrite by default to respect user customizations
346-
const autoFlags: string[] = [];
347-
if (!forwardedOptions.includes("--yes")) {
348-
autoFlags.push("--yes");
349-
}
350-
const baseArgs = [
351-
"shadcn@latest",
352-
"add",
353-
...packages,
354-
...autoFlags,
355-
...forwardedOptions,
356-
];
357-
358-
let cmd = "npx";
359-
let args: string[] = ["-y", ...baseArgs];
360-
if (runner === "bunx") {
361-
cmd = "bunx";
362-
args = baseArgs;
363-
} else if (runner === "pnpm dlx") {
364-
cmd = "pnpm";
365-
args = ["dlx", ...baseArgs];
366-
} else if (runner === "npx") {
367-
cmd = "npx";
368-
args = ["-y", ...baseArgs];
369-
}
343+
const shadcnArgs = buildShadcnAddArgs(packages, forwardedOptions);
344+
const invocation = buildPinnedShadcnInvocation(shadcnArgs);
370345

371346
if (isVerbose) {
372-
logger.info(`[bejamas-ui] ${cmd} ${args.join(" ")}`);
347+
logger.info(`[bejamas-ui] ${invocation.cmd} ${invocation.args.join(" ")}`);
373348
}
374349

375350
// Show our own spinner for checking registry
@@ -379,7 +354,7 @@ async function addComponents(
379354
try {
380355
// Run shadcn and capture output
381356
// Pipe "n" to stdin to answer "no" to any overwrite prompts (respects user customizations)
382-
const result = await execa(cmd, args, {
357+
const result = await execa(invocation.cmd, invocation.args, {
383358
env,
384359
input: "n\nn\nn\nn\nn\nn\nn\nn\nn\nn\n", // Answer "no" to up to 10 overwrite prompts
385360
stdout: "pipe",
@@ -425,9 +400,28 @@ async function addComponents(
425400
}
426401
}
427402

403+
export function buildShadcnAddArgs(
404+
packages: string[],
405+
forwardedOptions: string[],
406+
) {
407+
// Always pass --yes for non-interactive mode (skips "Add components?" confirmation)
408+
// Note: we don't pass --overwrite by default to respect user customizations
409+
const autoFlags: string[] = [];
410+
if (!forwardedOptions.includes("--yes")) {
411+
autoFlags.push("--yes");
412+
}
413+
414+
return [
415+
"add",
416+
...packages,
417+
...autoFlags,
418+
...forwardedOptions,
419+
];
420+
}
421+
428422
export const add = new Command()
429423
.name("add")
430-
.description("Add components via shadcn@latest using registry URLs")
424+
.description("Add components via the pinned shadcn registry flow")
431425
.argument("[components...]", "Component package names to add")
432426
.option("-y, --yes", "skip confirmation prompt.", false)
433427
.option("-o, --overwrite", "overwrite existing files.", false)
Lines changed: 6 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { promises as fs } from "fs";
21
import path from "path";
32
import { preFlightInit } from "@/src/preflights/preflight-init";
43

@@ -10,9 +9,9 @@ import { getConfig } from "@/src/utils/get-config";
109
import { handleError } from "@/src/utils/handle-error";
1110
import { highlighter } from "@/src/utils/highlighter";
1211
import { logger } from "@/src/utils/logger";
12+
import { buildPinnedShadcnInvocation } from "@/src/utils/shadcn-cli";
1313
import { Command } from "commander";
1414
import { execa } from "execa";
15-
import fsExtra from "fs-extra";
1615
import { z } from "zod";
1716

1817
// process.on("exit", (code) => {
@@ -30,7 +29,6 @@ import { z } from "zod";
3029
// Default fallback registry endpoint for shadcn (expects /r)
3130
const DEFAULT_REGISTRY_URL = "https://ui.bejamas.com/r";
3231
export const DEFAULT_COMPONENTS_BASE_COLOR = "neutral";
33-
const SHADCN_INIT_ARGS = ["init"] as const;
3432

3533
export const initOptionsSchema = z.object({
3634
cwd: z.string(),
@@ -60,121 +58,10 @@ export const initOptionsSchema = z.object({
6058
baseStyle: z.boolean(),
6159
});
6260

63-
export function buildShadcnInitInvocation(
64-
localShadcnPath: string,
65-
hasLocalShadcn: boolean,
66-
localShadcnVersion?: string | null,
67-
) {
68-
if (hasLocalShadcn) {
69-
const args = [...SHADCN_INIT_ARGS];
70-
if (usesLegacyBaseColorFlag(localShadcnVersion)) {
71-
args.push("--base-color", DEFAULT_COMPONENTS_BASE_COLOR);
72-
}
73-
74-
return {
75-
cmd: localShadcnPath,
76-
args,
77-
};
78-
}
79-
80-
return {
81-
cmd: "npx",
82-
args: ["-y", "shadcn@latest", ...SHADCN_INIT_ARGS],
83-
};
84-
}
85-
86-
export function usesLegacyBaseColorFlag(version?: string | null) {
87-
if (!version) {
88-
return false;
89-
}
90-
91-
const major = Number.parseInt(version.split(".")[0] ?? "", 10);
92-
return Number.isFinite(major) && major > 0 && major < 4;
93-
}
94-
95-
export async function getLocalShadcnVersion(cwd: string) {
96-
const filePath = path.resolve(cwd, "node_modules", "shadcn", "package.json");
97-
98-
try {
99-
const contents = await fs.readFile(filePath, "utf8");
100-
const parsed = JSON.parse(contents) as { version?: unknown };
101-
return typeof parsed.version === "string" ? parsed.version : null;
102-
} catch {
103-
return null;
104-
}
105-
}
106-
107-
export function getDeprecatedBaseColorWarning(baseColor?: string) {
108-
if (!baseColor) {
109-
return null;
110-
}
111-
112-
return [
113-
"The --base-color option is deprecated and ignored.",
114-
"Bejamas now aligns with shadcn CLI v4.",
115-
`Edit ${highlighter.info("components.json")} to change`,
116-
`${highlighter.info("tailwind.baseColor")} after init.`,
117-
].join(" ");
118-
}
119-
120-
export async function normalizeComponentsBaseColor(
121-
cwd: string,
61+
export function buildShadcnInitArgs(
12262
baseColor = DEFAULT_COMPONENTS_BASE_COLOR,
12363
) {
124-
const filePath = path.resolve(cwd, "components.json");
125-
126-
let originalContents: string;
127-
try {
128-
originalContents = await fs.readFile(filePath, "utf8");
129-
} catch {
130-
throw new Error(
131-
`Failed to read ${highlighter.info(
132-
filePath,
133-
)} after shadcn init. Make sure the command completed successfully and created a valid components.json file.`,
134-
);
135-
}
136-
137-
let parsedConfig: Record<string, unknown>;
138-
try {
139-
const parsed = JSON.parse(originalContents);
140-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
141-
throw new Error("Expected a JSON object.");
142-
}
143-
parsedConfig = parsed as Record<string, unknown>;
144-
} catch {
145-
throw new Error(
146-
`Failed to parse ${highlighter.info(
147-
filePath,
148-
)} after shadcn init. Expected a valid JSON object with a tailwind configuration.`,
149-
);
150-
}
151-
152-
const tailwind = parsedConfig.tailwind;
153-
if (!tailwind || typeof tailwind !== "object" || Array.isArray(tailwind)) {
154-
throw new Error(
155-
`Invalid ${highlighter.info(
156-
filePath,
157-
)} generated by shadcn init. Expected a ${highlighter.info(
158-
"tailwind",
159-
)} object so Bejamas can normalize ${highlighter.info("baseColor")}.`,
160-
);
161-
}
162-
163-
const normalizedConfig = {
164-
...parsedConfig,
165-
tailwind: {
166-
...(tailwind as Record<string, unknown>),
167-
baseColor,
168-
},
169-
};
170-
const normalizedContents = `${JSON.stringify(normalizedConfig, null, 2)}\n`;
171-
172-
if (normalizedContents === originalContents) {
173-
return false;
174-
}
175-
176-
await fs.writeFile(filePath, normalizedContents, "utf8");
177-
return true;
64+
return ["init", "--base-color", baseColor];
17865
}
17966

18067
export const init = new Command()
@@ -187,7 +74,7 @@ export const init = new Command()
18774
)
18875
.option(
18976
"-b, --base-color <base-color>",
190-
"deprecated: accepted for compatibility but ignored. Edit components.json to change tailwind.baseColor.",
77+
"the base color to use. (neutral, gray, zinc, stone, slate)",
19178
undefined,
19279
)
19380
.option("-y, --yes", "skip confirmation prompt.", true)
@@ -227,11 +114,6 @@ export async function runInit(
227114
skipPreflight?: boolean;
228115
},
229116
) {
230-
const baseColorWarning = getDeprecatedBaseColorWarning(options.baseColor);
231-
if (baseColorWarning && !options.silent) {
232-
logger.warn(baseColorWarning);
233-
}
234-
235117
let newProjectTemplate;
236118
if (!options.skipPreflight) {
237119
const preflight = await preFlightInit(options);
@@ -266,27 +148,13 @@ export async function runInit(
266148

267149
// const projectConfig = await getProjectConfig(options.cwd, projectInfo);
268150

269-
const shadcnBin = process.platform === "win32" ? "shadcn.cmd" : "shadcn";
270-
const localShadcnPath = path.resolve(
271-
options.cwd,
272-
"node_modules",
273-
".bin",
274-
shadcnBin,
275-
);
276-
277151
try {
278152
const env = {
279153
...process.env,
280154
REGISTRY_URL: process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL,
281155
};
282-
const hasLocalShadcn = await fsExtra.pathExists(localShadcnPath);
283-
const localShadcnVersion = hasLocalShadcn
284-
? await getLocalShadcnVersion(options.cwd)
285-
: null;
286-
const invocation = buildShadcnInitInvocation(
287-
localShadcnPath,
288-
hasLocalShadcn,
289-
localShadcnVersion,
156+
const invocation = buildPinnedShadcnInvocation(
157+
buildShadcnInitArgs(options.baseColor ?? DEFAULT_COMPONENTS_BASE_COLOR),
290158
);
291159

292160
await execa(invocation.cmd, invocation.args, {
@@ -298,6 +166,4 @@ export async function runInit(
298166
// shadcn already printed the detailed error to stdio, avoid double-reporting
299167
process.exit(1);
300168
}
301-
302-
await normalizeComponentsBaseColor(options.cwd);
303169
}

packages/bejamas/src/registry/errors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ export class ConfigMissingError extends RegistryError {
274274
code: RegistryErrorCode.NOT_CONFIGURED,
275275
context: { cwd },
276276
suggestion:
277-
"Run 'npx shadcn@latest init' to create a components.json file, or check that you're in the correct directory.",
277+
"Run 'npx bejamas init' to create a components.json file, or check that you're in the correct directory.",
278278
});
279279
this.name = "ConfigMissingError";
280280
}
@@ -298,7 +298,7 @@ export class ConfigParseError extends RegistryError {
298298
cause: parseError,
299299
context: { cwd },
300300
suggestion:
301-
"Check your components.json file for syntax errors or invalid configuration. Run 'npx shadcn@latest init' to regenerate a valid configuration.",
301+
"Check your components.json file for syntax errors or invalid configuration. Run 'npx bejamas init' to regenerate a valid configuration.",
302302
});
303303
this.name = "ConfigParseError";
304304
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { createRequire } from "module";
2+
3+
const require = createRequire(import.meta.url);
4+
5+
export const PINNED_SHADCN_VERSION = "3.8.5";
6+
export const PINNED_SHADCN_PACKAGE = `shadcn@${PINNED_SHADCN_VERSION}`;
7+
8+
export interface ShadcnInvocation {
9+
cmd: string;
10+
args: string[];
11+
source: "bundled" | "fallback";
12+
}
13+
14+
export function resolveBundledShadcnEntrypoint() {
15+
try {
16+
return require.resolve("shadcn");
17+
} catch {
18+
return null;
19+
}
20+
}
21+
22+
export function buildPinnedShadcnInvocation(
23+
shadcnArgs: string[],
24+
bundledEntrypoint = resolveBundledShadcnEntrypoint(),
25+
): ShadcnInvocation {
26+
if (bundledEntrypoint) {
27+
return {
28+
cmd: process.execPath,
29+
args: [bundledEntrypoint, ...shadcnArgs],
30+
source: "bundled",
31+
};
32+
}
33+
34+
return {
35+
cmd: "npx",
36+
args: ["-y", PINNED_SHADCN_PACKAGE, ...shadcnArgs],
37+
source: "fallback",
38+
};
39+
}

0 commit comments

Comments
 (0)