bejamas 0.2.12 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,21 +1,30 @@
1
1
  #!/usr/bin/env node
2
- import { _ as getWorkspaceConfig, b as highlighter, d as parseJsDocMetadata, g as getConfig, o as extractFrontmatter, p as resolveUiRoot, v as spinner, y as logger } from "./utils-BfJTJvcy.js";
2
+ import { A as renderSemanticIconSvgWithAttributeString, B as normalizeDesignSystemConfig, C as buildUiUrl, D as SEMANTIC_ICON_NAMES, E as ICON_LIBRARY_COLLECTIONS, F as getDocumentLanguage, H as encodePreset, I as getFontPackageName, L as getFontValue, M as RTL_LANGUAGE_VALUES, N as designSystemConfigSchema, O as getSemanticIconNameFromLucideExport, P as getDocumentDirection, R as getHeadingFontValue, S as BASE_COLORS, T as buildRegistryTheme, U as isPresetCode, V as decodePreset, W as fonts, _ as getConfig, b as highlighter, d as parseJsDocMetadata, g as spinner, j as DEFAULT_DESIGN_SYSTEM_CONFIG, k as getSemanticIconNameFromLucidePath, o as extractFrontmatter, p as resolveUiRoot, v as getWorkspaceConfig, w as resolveRegistryUrl, x as BEJAMAS_COMPONENTS_SCHEMA_URL, y as logger, z as getStyleId } from "./utils-BCEkpKMO.js";
3
3
  import { Command } from "commander";
4
4
  import { createRequire } from "module";
5
- import path from "path";
5
+ import path, { extname, isAbsolute, join, relative, resolve } from "node:path";
6
+ import { z } from "zod";
7
+ import path$1 from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { existsSync, promises, readFileSync, readdirSync } from "node:fs";
6
10
  import fs from "fs-extra";
11
+ import fg from "fast-glob";
12
+ import fs$1 from "node:fs/promises";
13
+ import { bold, cyan, dim, green, magenta, red, white, yellow } from "kleur/colors";
7
14
  import os from "os";
8
15
  import dotenv from "dotenv";
9
16
  import { detect } from "@antfu/ni";
10
- import { z } from "zod";
11
- import { bold, cyan, dim, green, magenta, red, white, yellow } from "kleur/colors";
12
17
  import { execa } from "execa";
13
18
  import prompts from "prompts";
14
- import fg from "fast-glob";
15
- import path$1, { extname as extname$1, isAbsolute, join as join$1, relative as relative$1, resolve } from "node:path";
16
- import { existsSync, readFileSync, readdirSync } from "node:fs";
17
- import fs$1 from "node:fs/promises";
19
+ import os$1 from "node:os";
20
+
21
+ //#region src/registry/context.ts
22
+ let context = { headers: {} };
23
+ function clearRegistryContext() {
24
+ context.headers = {};
25
+ }
18
26
 
27
+ //#endregion
19
28
  //#region src/utils/errors.ts
20
29
  const MISSING_DIR_OR_EMPTY_PROJECT = "1";
21
30
 
@@ -23,7 +32,7 @@ const MISSING_DIR_OR_EMPTY_PROJECT = "1";
23
32
  //#region src/preflights/preflight-init.ts
24
33
  async function preFlightInit(options) {
25
34
  const errors = {};
26
- if (!fs.existsSync(options.cwd) || !fs.existsSync(path.resolve(options.cwd, "package.json"))) {
35
+ if (!fs.existsSync(options.cwd) || !fs.existsSync(path$1.resolve(options.cwd, "package.json"))) {
27
36
  errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
28
37
  return {
29
38
  errors,
@@ -37,10 +46,986 @@ async function preFlightInit(options) {
37
46
  }
38
47
 
39
48
  //#endregion
40
- //#region src/registry/context.ts
41
- let context = { headers: {} };
42
- function clearRegistryContext() {
43
- context.headers = {};
49
+ //#region src/utils/astro-fonts.ts
50
+ const ASTRO_CONFIG_BLOCK_START = "// bejamas:astro-fonts:start";
51
+ const ASTRO_CONFIG_BLOCK_END = "// bejamas:astro-fonts:end";
52
+ const ASTRO_LAYOUT_BLOCK_START = "<!-- bejamas:astro-fonts:start -->";
53
+ const ASTRO_LAYOUT_BLOCK_END = "<!-- bejamas:astro-fonts:end -->";
54
+ const ASTRO_FONT_CONSTANT = "BEJAMAS_ASTRO_FONTS";
55
+ const ASTRO_CONFIG_CANDIDATES = [
56
+ "astro.config.mjs",
57
+ "apps/web/astro.config.mjs",
58
+ "apps/docs/astro.config.mjs"
59
+ ];
60
+ const ASTRO_LAYOUT_CANDIDATES = ["src/layouts/Layout.astro", "apps/web/src/layouts/Layout.astro"];
61
+ const STARLIGHT_HEAD_RELATIVE_PATH = "apps/docs/src/components/Head.astro";
62
+ const ASTRO_FONT_PROVIDERS = ["fontsource", "google"];
63
+ const ASTRO_FONTSOURCE_FONT_NAMES = new Set(["geist", "geist-mono"]);
64
+ const MANAGED_BODY_FONT_CLASSES = new Set(["font-mono", "font-serif"]);
65
+ function compactSource$1(source) {
66
+ return source.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
67
+ }
68
+ function escapeRegExp$1(value) {
69
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
70
+ }
71
+ function upsertImport(source, moduleName, importedName, fallbackNames = []) {
72
+ const importPattern = /* @__PURE__ */ new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']${escapeRegExp$1(moduleName)}["'];?`);
73
+ if (importPattern.test(source)) return source.replace(importPattern, (_match, importsSource) => {
74
+ const imports = importsSource.split(",").map((value) => value.trim()).filter(Boolean);
75
+ if (!imports.includes(importedName)) imports.push(importedName);
76
+ for (const name of fallbackNames) if (!imports.includes(name)) imports.unshift(name);
77
+ return `import { ${Array.from(new Set(imports)).join(", ")} } from "${moduleName}";`;
78
+ });
79
+ const importLine = `import { ${Array.from(new Set([...fallbackNames, importedName])).join(", ")} } from "${moduleName}";`;
80
+ const frontmatterEnd = source.indexOf("---", 3);
81
+ if (source.startsWith("---") && frontmatterEnd !== -1) return `${source.slice(0, frontmatterEnd).trimEnd()}\n${importLine}\n${source.slice(frontmatterEnd)}`;
82
+ return `${importLine}\n${source}`;
83
+ }
84
+ function buildAstroConfigFontBlock(fontsToSerialize) {
85
+ return `${ASTRO_CONFIG_BLOCK_START}\n/** @type {NonNullable<import("astro/config").AstroUserConfig["fonts"]>} */\nconst ${ASTRO_FONT_CONSTANT} = [${fontsToSerialize.length === 0 ? "" : `\n${fontsToSerialize.map((font) => ` {\n provider: fontProviders.${font.provider}(),\n name: ${JSON.stringify(font.name)},\n cssVariable: ${JSON.stringify(font.cssVariable)},\n subsets: ${JSON.stringify(font.subsets)},\n },`).join("\n")}\n`}];\n${ASTRO_CONFIG_BLOCK_END}`;
86
+ }
87
+ function buildAstroLayoutFontBlock(fontsToSerialize) {
88
+ return `${ASTRO_LAYOUT_BLOCK_START}\n${fontsToSerialize.map((font) => `<Font cssVariable="${font.cssVariable}" />`).join("\n")}\n${ASTRO_LAYOUT_BLOCK_END}`;
89
+ }
90
+ function bodyFontClassFromCssVariable(cssVariable) {
91
+ if (cssVariable === "--font-mono" || cssVariable === "--font-serif") return fontClassFromCssVariable(cssVariable);
92
+ return "";
93
+ }
94
+ function upsertManagedBodyFontClass(source, cssVariable) {
95
+ const bodyClass = bodyFontClassFromCssVariable(cssVariable);
96
+ const bodyTagPattern = /<body\b([^>]*?)(\/?)>/m;
97
+ if (!bodyTagPattern.test(source)) return source;
98
+ return source.replace(bodyTagPattern, (_match, attrs, selfClosing) => {
99
+ const classPattern = /\sclass=(["'])(.*?)\1/m;
100
+ const classMatch = attrs.match(classPattern);
101
+ const quote = classMatch?.[1] ?? "\"";
102
+ const existingClasses = (classMatch?.[2] ?? "").split(/\s+/).filter(Boolean).filter((className) => !MANAGED_BODY_FONT_CLASSES.has(className));
103
+ const nextClasses = bodyClass ? Array.from(new Set([...existingClasses, bodyClass])) : existingClasses;
104
+ let nextAttrs = attrs.replace(classPattern, "");
105
+ if (nextClasses.length > 0) nextAttrs = `${nextAttrs} class=${quote}${nextClasses.join(" ")}${quote}`;
106
+ return `<body${nextAttrs}${selfClosing}>`;
107
+ });
108
+ }
109
+ function parseManagedAstroFonts(source) {
110
+ const markerPattern = /* @__PURE__ */ new RegExp(`${escapeRegExp$1(ASTRO_CONFIG_BLOCK_START)}[\\s\\S]*?const\\s+${ASTRO_FONT_CONSTANT}\\s*=\\s*\\[([\\s\\S]*?)\\];[\\s\\S]*?${escapeRegExp$1(ASTRO_CONFIG_BLOCK_END)}`);
111
+ const match = source.match(markerPattern);
112
+ if (!match) return [];
113
+ return (match[1].match(/\{[\s\S]*?\}/g) ?? []).map((entry) => {
114
+ const name = entry.match(/name:\s*"([^"]+)"/)?.[1];
115
+ const cssVariable = entry.match(/cssVariable:\s*"([^"]+)"/)?.[1];
116
+ const provider = entry.match(/provider:\s*fontProviders\.([a-z]+)\(\)/)?.[1];
117
+ const subsetsSource = entry.match(/subsets:\s*(\[[^\]]*\])/s)?.[1];
118
+ if (!name || !cssVariable || !provider || !ASTRO_FONT_PROVIDERS.includes(provider)) return null;
119
+ let subsets = ["latin"];
120
+ if (subsetsSource) try {
121
+ const parsed = JSON.parse(subsetsSource);
122
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((value) => typeof value === "string")) subsets = parsed;
123
+ } catch {
124
+ subsets = ["latin"];
125
+ }
126
+ return {
127
+ name,
128
+ cssVariable,
129
+ provider,
130
+ subsets
131
+ };
132
+ }).filter((value) => value !== null);
133
+ }
134
+ function normalizeManagedAstroFontSubsets(subsets) {
135
+ if (!subsets?.length) return ["latin"];
136
+ return [subsets[0], ...subsets.slice(1)];
137
+ }
138
+ function replaceManagedBlock(source, nextBlock, start, end) {
139
+ const blockPattern = new RegExp(`${escapeRegExp$1(start)}[\\s\\S]*?${escapeRegExp$1(end)}`, "m");
140
+ if (blockPattern.test(source)) return source.replace(blockPattern, nextBlock);
141
+ return null;
142
+ }
143
+ function stripExperimentalFontsProperty(source) {
144
+ const experimentalMatch = source.match(/experimental\s*:\s*\{/);
145
+ if (!experimentalMatch || experimentalMatch.index === void 0) return source;
146
+ const propertyStart = experimentalMatch.index;
147
+ const openingBraceIndex = propertyStart + experimentalMatch[0].lastIndexOf("{");
148
+ let depth = 0;
149
+ let closingBraceIndex = -1;
150
+ let quote = null;
151
+ for (let index = openingBraceIndex; index < source.length; index += 1) {
152
+ const character = source[index];
153
+ const previousCharacter = index > 0 ? source[index - 1] : "";
154
+ if (quote) {
155
+ if (character === quote && previousCharacter !== "\\") quote = null;
156
+ continue;
157
+ }
158
+ if (character === "\"" || character === "'" || character === "`") {
159
+ quote = character;
160
+ continue;
161
+ }
162
+ if (character === "{") {
163
+ depth += 1;
164
+ continue;
165
+ }
166
+ if (character === "}") {
167
+ depth -= 1;
168
+ if (depth === 0) {
169
+ closingBraceIndex = index;
170
+ break;
171
+ }
172
+ }
173
+ }
174
+ if (closingBraceIndex === -1) return source;
175
+ const nextBlockContent = source.slice(openingBraceIndex + 1, closingBraceIndex).replace(/(^|\n)\s*fonts\s*:\s*BEJAMAS_ASTRO_FONTS\s*,?\s*(?=\n|$)/g, "$1").replace(/\n{3,}/g, "\n\n");
176
+ let propertyEnd = closingBraceIndex + 1;
177
+ while (propertyEnd < source.length && /\s/.test(source[propertyEnd])) propertyEnd += 1;
178
+ if (source[propertyEnd] === ",") propertyEnd += 1;
179
+ if (nextBlockContent.trim().length === 0) return `${source.slice(0, propertyStart)}${source.slice(propertyEnd)}`;
180
+ const rebuiltProperty = `experimental: {${nextBlockContent}\n }`;
181
+ return `${source.slice(0, propertyStart)}${rebuiltProperty}${source.slice(closingBraceIndex + 1)}`;
182
+ }
183
+ function patchAstroConfigSource(source, fontsToSerialize, options = {}) {
184
+ let next = upsertImport(source, "astro/config", "fontProviders", ["defineConfig"]);
185
+ const fontBlock = buildAstroConfigFontBlock(fontsToSerialize);
186
+ const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_CONFIG_BLOCK_START, ASTRO_CONFIG_BLOCK_END);
187
+ if (replacedBlock) next = replacedBlock;
188
+ else {
189
+ const lastImport = Array.from(next.matchAll(/^import .*$/gm)).at(-1);
190
+ if (lastImport?.index !== void 0) {
191
+ const insertAt = lastImport.index + lastImport[0].length;
192
+ next = `${next.slice(0, insertAt)}\n\n${fontBlock}${next.slice(insertAt)}`;
193
+ } else next = `${fontBlock}\n\n${next}`;
194
+ }
195
+ next = stripExperimentalFontsProperty(next);
196
+ if (!/^\s*fonts:\s*BEJAMAS_ASTRO_FONTS\b/m.test(next)) next = next.replace(/defineConfig\(\s*\{/, `defineConfig({\n fonts: ${ASTRO_FONT_CONSTANT},`);
197
+ if (options.starlightHeadOverride) {
198
+ if (next.includes("Head: \"./src/components/Head.astro\"")) return compactSource$1(next);
199
+ const starlightComponentsPattern = /starlight\(\{\s*([\s\S]*?)components:\s*\{([\s\S]*?)\}/m;
200
+ if (starlightComponentsPattern.test(next)) next = next.replace(starlightComponentsPattern, (_match, before, inner) => `starlight({${before}components: {\n Head: "./src/components/Head.astro",${inner ? `${inner}` : ""}\n }`);
201
+ else next = next.replace(/starlight\(\s*\{/, `starlight({\n components: {\n Head: "./src/components/Head.astro",\n },`);
202
+ }
203
+ return compactSource$1(next);
204
+ }
205
+ function patchAstroLayoutSource(source, fontsToSerialize, activeFontCssVariable) {
206
+ let next = upsertImport(source, "astro:assets", "Font");
207
+ const fontBlock = buildAstroLayoutFontBlock(fontsToSerialize);
208
+ const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_LAYOUT_BLOCK_START, ASTRO_LAYOUT_BLOCK_END);
209
+ if (replacedBlock) next = replacedBlock;
210
+ else if (next.includes("<head>")) next = next.replace("<head>", `<head>\n ${fontBlock.replace(/\n/g, "\n ")}`);
211
+ else if (next.includes("</head>")) next = next.replace("</head>", ` ${fontBlock.replace(/\n/g, "\n ")}\n</head>`);
212
+ else next = `${next}\n${fontBlock}\n`;
213
+ next = upsertManagedBodyFontClass(next, activeFontCssVariable);
214
+ return compactSource$1(next);
215
+ }
216
+ function patchStarlightHeadSource(source, fontsToSerialize) {
217
+ let next = upsertImport(source, "@astrojs/starlight/components/Head.astro", "DefaultHead");
218
+ next = upsertImport(next, "astro:assets", "Font");
219
+ const fontBlock = buildAstroLayoutFontBlock(fontsToSerialize);
220
+ const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_LAYOUT_BLOCK_START, ASTRO_LAYOUT_BLOCK_END);
221
+ if (replacedBlock) next = replacedBlock;
222
+ else if (next.includes("<DefaultHead")) next = next.replace(/<DefaultHead[^>]*\/>/, (match) => `${match}\n${fontBlock}`);
223
+ else next = `${next.trimEnd()}\n\n<DefaultHead />\n${fontBlock}\n`;
224
+ return compactSource$1(next);
225
+ }
226
+ function toManagedAstroFont(fontName) {
227
+ const normalized = fontName.replace(/^font-heading-/, "").replace(/^font-/, "");
228
+ const font = fontName.startsWith("font-heading-") ? getHeadingFontValue(normalized) : getFontValue(normalized);
229
+ if (!font || font.type !== "registry:font" || !ASTRO_FONT_PROVIDERS.includes(font.font.provider)) return null;
230
+ return {
231
+ name: font.title?.replace(/\s+\(Heading\)$/, "") ?? font.font.import.replace(/_/g, " "),
232
+ cssVariable: font.font.variable,
233
+ provider: ASTRO_FONTSOURCE_FONT_NAMES.has(normalized) ? "fontsource" : "google",
234
+ subsets: normalizeManagedAstroFontSubsets(font.font.subsets)
235
+ };
236
+ }
237
+ function mergeManagedAstroFonts(currentFonts, nextFont) {
238
+ return [...currentFonts.filter((font) => font.cssVariable !== nextFont.cssVariable), nextFont].sort((left, right) => left.cssVariable.localeCompare(right.cssVariable));
239
+ }
240
+ function fontClassFromCssVariable(cssVariable) {
241
+ return `font-${cssVariable.replace(/^--font-/, "")}`;
242
+ }
243
+ async function patchFileIfExists(filePath, transformer) {
244
+ if (!await fs.pathExists(filePath)) return;
245
+ const current = await fs.readFile(filePath, "utf8");
246
+ const next = transformer(current);
247
+ if (next !== current) await fs.writeFile(filePath, next, "utf8");
248
+ }
249
+ async function collectPackageJsonsFromComponents(projectPath) {
250
+ const componentJsonFiles = await fg("**/components.json", {
251
+ cwd: projectPath,
252
+ ignore: [
253
+ "**/node_modules/**",
254
+ "**/dist/**",
255
+ "**/.astro/**"
256
+ ]
257
+ });
258
+ const packageJsonFiles = /* @__PURE__ */ new Set();
259
+ for (const relativePath of componentJsonFiles) {
260
+ const absolutePath = path.resolve(projectPath, relativePath);
261
+ const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
262
+ if (typeof cssPath !== "string" || cssPath.length === 0) continue;
263
+ let currentDir = path.dirname(path.resolve(path.dirname(absolutePath), cssPath));
264
+ while (true) {
265
+ const packageJsonPath = path.resolve(currentDir, "package.json");
266
+ if (await fs.pathExists(packageJsonPath)) {
267
+ packageJsonFiles.add(packageJsonPath);
268
+ break;
269
+ }
270
+ const parentDir = path.dirname(currentDir);
271
+ if (parentDir === currentDir) break;
272
+ currentDir = parentDir;
273
+ }
274
+ }
275
+ return packageJsonFiles;
276
+ }
277
+ async function upsertStarlightHeadFile(projectPath, fontsToSerialize) {
278
+ const headPath = path.resolve(projectPath, STARLIGHT_HEAD_RELATIVE_PATH);
279
+ const next = patchStarlightHeadSource(await fs.pathExists(headPath) ? await fs.readFile(headPath, "utf8") : "---\nimport DefaultHead from \"@astrojs/starlight/components/Head.astro\";\nimport { Font } from \"astro:assets\";\n---\n\n<DefaultHead />\n", fontsToSerialize);
280
+ await fs.ensureDir(path.dirname(headPath));
281
+ await fs.writeFile(headPath, next, "utf8");
282
+ }
283
+ async function readManagedAstroFontsFromProject(projectPath) {
284
+ for (const relativePath of ASTRO_CONFIG_CANDIDATES) {
285
+ const absolutePath = path.resolve(projectPath, relativePath);
286
+ if (!await fs.pathExists(absolutePath)) continue;
287
+ const parsed = parseManagedAstroFonts(await fs.readFile(absolutePath, "utf8"));
288
+ if (parsed.length > 0) return parsed;
289
+ }
290
+ return [];
291
+ }
292
+ async function cleanupAstroFontPackages(projectPath) {
293
+ const managedPackages = new Set(fonts.filter((font) => font.type === "registry:font").map((font) => getFontPackageName(font.name.replace(/^font-heading-/, "").replace(/^font-/, ""))));
294
+ const packageJsonFiles = await collectPackageJsonsFromComponents(projectPath);
295
+ await Promise.all(Array.from(packageJsonFiles).map(async (absolutePath) => {
296
+ const current = await fs.readJson(absolutePath);
297
+ let changed = false;
298
+ for (const bucket of ["dependencies", "devDependencies"]) {
299
+ const currentBucket = current[bucket];
300
+ if (!currentBucket || typeof currentBucket !== "object") continue;
301
+ for (const packageName of managedPackages) if (packageName in currentBucket) {
302
+ delete currentBucket[packageName];
303
+ changed = true;
304
+ }
305
+ if (Object.keys(currentBucket).length === 0) {
306
+ delete current[bucket];
307
+ changed = true;
308
+ }
309
+ }
310
+ if (changed) await fs.writeJson(absolutePath, current, { spaces: 2 });
311
+ }));
312
+ }
313
+ async function syncAstroFontsInProject(projectPath, fontsToSerialize, activeFontCssVariable) {
314
+ await Promise.all(ASTRO_CONFIG_CANDIDATES.map((relativePath) => patchFileIfExists(path.resolve(projectPath, relativePath), (source) => patchAstroConfigSource(source, fontsToSerialize, { starlightHeadOverride: relativePath === "apps/docs/astro.config.mjs" }))));
315
+ await Promise.all(ASTRO_LAYOUT_CANDIDATES.map((relativePath) => patchFileIfExists(path.resolve(projectPath, relativePath), (source) => patchAstroLayoutSource(source, fontsToSerialize, activeFontCssVariable))));
316
+ const docsConfigPath = path.resolve(projectPath, "apps/docs/astro.config.mjs");
317
+ if (await fs.pathExists(docsConfigPath)) await upsertStarlightHeadFile(projectPath, fontsToSerialize);
318
+ }
319
+
320
+ //#endregion
321
+ //#region src/utils/apply-design-system.ts
322
+ const CREATE_BLOCK_START = "/* bejamas:create:start */";
323
+ const CREATE_BLOCK_END = "/* bejamas:create:end */";
324
+ const SHADCN_TAILWIND_IMPORT = "@import \"shadcn/tailwind.css\";";
325
+ const BEJAMAS_TAILWIND_IMPORT = "@import \"bejamas/tailwind.css\";";
326
+ const MANAGED_TAILWIND_IMPORTS = new Set([SHADCN_TAILWIND_IMPORT, BEJAMAS_TAILWIND_IMPORT]);
327
+ const TEMPLATE_APP_UI_IMPORT = "import { appUi } from \"@/i18n/ui\";";
328
+ const TEMPLATE_I18N_SOURCES = {
329
+ astro: `const RTL_LANGUAGES = ["ar", "fa", "he"] as const;
330
+
331
+ export type TemplateLanguage = "en" | (typeof RTL_LANGUAGES)[number];
332
+
333
+ export const CURRENT_LANGUAGE: TemplateLanguage = "en";
334
+
335
+ const ui = {
336
+ en: {
337
+ metadataTitle: "bejamas/ui Astro project",
338
+ welcomeMessage: "Welcome to {project} Astro project.",
339
+ getStartedMessage: "Get started by editing src/pages/index.astro.",
340
+ readDocs: "Read docs",
341
+ getCustomDemo: "Get custom demo",
342
+ },
343
+ ar: {
344
+ metadataTitle: "مشروع Astro من bejamas/ui",
345
+ welcomeMessage: "مرحبًا بك في مشروع Astro الخاص بـ {project}.",
346
+ getStartedMessage: "ابدأ بتحرير src/pages/index.astro.",
347
+ readDocs: "اقرأ التوثيق",
348
+ getCustomDemo: "احصل على عرض توضيحي مخصص",
349
+ },
350
+ fa: {
351
+ metadataTitle: "پروژه Astro با bejamas/ui",
352
+ welcomeMessage: "به پروژه Astro با {project} خوش آمدید.",
353
+ getStartedMessage: "برای شروع src/pages/index.astro را ویرایش کنید.",
354
+ readDocs: "مطالعه مستندات",
355
+ getCustomDemo: "درخواست دموی سفارشی",
356
+ },
357
+ he: {
358
+ metadataTitle: "פרויקט Astro עם bejamas/ui",
359
+ welcomeMessage: "ברוכים הבאים לפרויקט Astro של {project}.",
360
+ getStartedMessage: "התחילו בעריכת src/pages/index.astro.",
361
+ readDocs: "קריאת התיעוד",
362
+ getCustomDemo: "קבלת דמו מותאם",
363
+ },
364
+ } as const satisfies Record<TemplateLanguage, Record<string, string>>;
365
+
366
+ export const appUi = {
367
+ lang: CURRENT_LANGUAGE,
368
+ dir: RTL_LANGUAGES.includes(
369
+ CURRENT_LANGUAGE as (typeof RTL_LANGUAGES)[number],
370
+ )
371
+ ? "rtl"
372
+ : "ltr",
373
+ ...ui[CURRENT_LANGUAGE],
374
+ };
375
+ `,
376
+ monorepo: `const RTL_LANGUAGES = ["ar", "fa", "he"] as const;
377
+
378
+ export type TemplateLanguage = "en" | (typeof RTL_LANGUAGES)[number];
379
+
380
+ export const CURRENT_LANGUAGE: TemplateLanguage = "en";
381
+
382
+ const ui = {
383
+ en: {
384
+ metadataTitle: "bejamas/ui Astro project",
385
+ welcomeMessage: "Welcome to {project} Astro monorepo.",
386
+ getStartedMessage: "Get started by editing apps/web/src/pages/index.astro.",
387
+ readDocs: "Read docs",
388
+ getCustomDemo: "Get custom demo",
389
+ },
390
+ ar: {
391
+ metadataTitle: "مشروع Astro من bejamas/ui",
392
+ welcomeMessage: "مرحبًا بك في مشروع Astro الأحادي من {project}.",
393
+ getStartedMessage: "ابدأ بتحرير apps/web/src/pages/index.astro.",
394
+ readDocs: "اقرأ التوثيق",
395
+ getCustomDemo: "احصل على عرض توضيحي مخصص",
396
+ },
397
+ fa: {
398
+ metadataTitle: "پروژه Astro با bejamas/ui",
399
+ welcomeMessage: "به مونوریپوی Astro با {project} خوش آمدید.",
400
+ getStartedMessage:
401
+ "برای شروع apps/web/src/pages/index.astro را ویرایش کنید.",
402
+ readDocs: "مطالعه مستندات",
403
+ getCustomDemo: "درخواست دموی سفارشی",
404
+ },
405
+ he: {
406
+ metadataTitle: "פרויקט Astro עם bejamas/ui",
407
+ welcomeMessage: "ברוכים הבאים למונורפו Astro של {project}.",
408
+ getStartedMessage: "התחילו בעריכת apps/web/src/pages/index.astro.",
409
+ readDocs: "קריאת התיעוד",
410
+ getCustomDemo: "קבלת דמו מותאם",
411
+ },
412
+ } as const satisfies Record<TemplateLanguage, Record<string, string>>;
413
+
414
+ export const appUi = {
415
+ lang: CURRENT_LANGUAGE,
416
+ dir: RTL_LANGUAGES.includes(
417
+ CURRENT_LANGUAGE as (typeof RTL_LANGUAGES)[number],
418
+ )
419
+ ? "rtl"
420
+ : "ltr",
421
+ ...ui[CURRENT_LANGUAGE],
422
+ };
423
+ `
424
+ };
425
+ const TEMPLATE_PAGE_VARIANTS = [
426
+ {
427
+ matchers: [
428
+ "import { Button } from \"@/ui/button\";",
429
+ "Astro project.",
430
+ "Get started by editing src/pages/index.astro."
431
+ ],
432
+ i18nSource: `---
433
+ import Layout from "@/layouts/Layout.astro";
434
+ import { appUi } from "@/i18n/ui";
435
+ import { Button } from "@/ui/button";
436
+
437
+ const [welcomePrefix, welcomeSuffix = ""] =
438
+ appUi.welcomeMessage.split("{project}");
439
+ ---
440
+
441
+ <Layout>
442
+ <div
443
+ class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
444
+ >
445
+ <div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
446
+ <img src="/bejamas.svg" alt="bejamas/ui" class="w-8 dark:invert" />
447
+ <div class="space-y-2">
448
+ <p class="text-sm">
449
+ {welcomePrefix}<code
450
+ class="font-bold relative bg-muted rounded p-1"
451
+ >@bejamas/ui</code
452
+ >{welcomeSuffix}
453
+ </p>
454
+ <p class="text-sm">
455
+ {appUi.getStartedMessage}
456
+ </p>
457
+ </div>
458
+ <div class="flex gap-2">
459
+ <Button as="a" href="https://ui.bejamas.com/docs"
460
+ >{appUi.readDocs}</Button
461
+ >
462
+ <Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
463
+ >{appUi.getCustomDemo}</Button
464
+ >
465
+ </div>
466
+ </div>
467
+ </div>
468
+ </Layout>
469
+ `
470
+ },
471
+ {
472
+ matchers: [
473
+ "import { Button } from \"@repo/ui/components/button\";",
474
+ "width=\"32\"",
475
+ "Astro monorepo.",
476
+ "Get started by editing apps/web/src/pages/index.astro."
477
+ ],
478
+ i18nSource: `---
479
+ import Layout from "@/layouts/Layout.astro";
480
+ import { appUi } from "@/i18n/ui";
481
+ import { Button } from "@repo/ui/components/button";
482
+
483
+ const [welcomePrefix, welcomeSuffix = ""] =
484
+ appUi.welcomeMessage.split("{project}");
485
+ ---
486
+
487
+ <Layout>
488
+ <div
489
+ class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
490
+ >
491
+ <div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
492
+ <img
493
+ src="/bejamas.svg"
494
+ alt="bejamas/ui"
495
+ class="w-8 dark:invert"
496
+ width="32"
497
+ height="32"
498
+ alt="bejamas/ui logo"
499
+ />
500
+ <div class="space-y-2">
501
+ <p class="text-sm">
502
+ {welcomePrefix}<code
503
+ class="font-bold relative bg-muted rounded p-1"
504
+ >@bejamas/ui</code
505
+ >{welcomeSuffix}
506
+ </p>
507
+ <p class="text-sm">
508
+ {appUi.getStartedMessage}
509
+ </p>
510
+ </div>
511
+ <div class="flex gap-2">
512
+ <Button as="a" href="https://ui.bejamas.com/docs"
513
+ >{appUi.readDocs}</Button
514
+ >
515
+ <Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
516
+ >{appUi.getCustomDemo}</Button
517
+ >
518
+ </div>
519
+ </div>
520
+ </div>
521
+ </Layout>
522
+ `
523
+ },
524
+ {
525
+ matchers: [
526
+ "import { Button } from \"@repo/ui/components/button\";",
527
+ "<img src=\"/bejamas.svg\" alt=\"bejamas/ui\" class=\"w-8 dark:invert\" />",
528
+ "Astro monorepo.",
529
+ "Get started by editing apps/web/src/pages/index.astro."
530
+ ],
531
+ i18nSource: `---
532
+ import Layout from "@/layouts/Layout.astro";
533
+ import { appUi } from "@/i18n/ui";
534
+ import { Button } from "@repo/ui/components/button";
535
+
536
+ const [welcomePrefix, welcomeSuffix = ""] =
537
+ appUi.welcomeMessage.split("{project}");
538
+ ---
539
+
540
+ <Layout>
541
+ <div
542
+ class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
543
+ >
544
+ <div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
545
+ <img src="/bejamas.svg" alt="bejamas/ui" class="w-8 dark:invert" />
546
+ <div class="space-y-2">
547
+ <p class="text-sm">
548
+ {welcomePrefix}<code
549
+ class="font-bold relative bg-muted rounded p-1"
550
+ >@bejamas/ui</code
551
+ >{welcomeSuffix}
552
+ </p>
553
+ <p class="text-sm">
554
+ {appUi.getStartedMessage}
555
+ </p>
556
+ </div>
557
+ <div class="flex gap-2">
558
+ <Button as="a" href="https://ui.bejamas.com/docs"
559
+ >{appUi.readDocs}</Button
560
+ >
561
+ <Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
562
+ >{appUi.getCustomDemo}</Button
563
+ >
564
+ </div>
565
+ </div>
566
+ </div>
567
+ </Layout>
568
+ `
569
+ }
570
+ ];
571
+ function escapeRegExp(value) {
572
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
573
+ }
574
+ function compactCss(source) {
575
+ return source.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
576
+ }
577
+ function compactSource(source) {
578
+ return source.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
579
+ }
580
+ function stripLegacyCreateBlock(source) {
581
+ const fullBlockPattern = new RegExp(`\\n*${escapeRegExp(CREATE_BLOCK_START)}[\\s\\S]*?${escapeRegExp(CREATE_BLOCK_END)}\\n*`, "m");
582
+ const danglingBlockPattern = new RegExp(`\\n*${escapeRegExp(CREATE_BLOCK_START)}[\\s\\S]*$`, "m");
583
+ return compactCss(source.replace(fullBlockPattern, "\n\n").replace(danglingBlockPattern, "\n\n"));
584
+ }
585
+ function buildCssVarBlock(selector, vars) {
586
+ return [
587
+ `${selector} {`,
588
+ ...Object.entries(vars).map(([key, value]) => ` --${key}: ${value};`),
589
+ "}"
590
+ ].join("\n");
591
+ }
592
+ function replaceTopLevelBlock(source, selector, nextBlock) {
593
+ const pattern = new RegExp(`^${escapeRegExp(selector)}\\s*\\{[\\s\\S]*?^\\}`, "m");
594
+ if (pattern.test(source)) return source.replace(pattern, nextBlock);
595
+ const layerIndex = source.indexOf("@layer base");
596
+ if (layerIndex !== -1) return `${source.slice(0, layerIndex).trimEnd()}\n\n${nextBlock}\n\n${source.slice(layerIndex).trimStart()}`;
597
+ return `${source.trimEnd()}\n\n${nextBlock}\n`;
598
+ }
599
+ function upsertImports(source, imports) {
600
+ const cleanedLines = source.split("\n").filter((line) => {
601
+ const trimmed = line.trim();
602
+ if (MANAGED_TAILWIND_IMPORTS.has(trimmed)) return false;
603
+ return !trimmed.startsWith("@import \"@fontsource-variable/");
604
+ });
605
+ let insertAt = -1;
606
+ for (let index = 0; index < cleanedLines.length; index += 1) {
607
+ if (cleanedLines[index].trim().startsWith("@import ")) {
608
+ insertAt = index;
609
+ continue;
610
+ }
611
+ if (insertAt !== -1 && cleanedLines[index].trim() !== "") break;
612
+ }
613
+ const uniqueImports = imports.filter((line, index) => imports.indexOf(line) === index);
614
+ if (insertAt === -1) return compactCss([
615
+ ...uniqueImports,
616
+ "",
617
+ ...cleanedLines
618
+ ].join("\n"));
619
+ cleanedLines.splice(insertAt + 1, 0, ...uniqueImports);
620
+ return compactCss(cleanedLines.join("\n"));
621
+ }
622
+ function upsertManagedTailwindImport(source) {
623
+ const nextImport = resolveManagedTailwindImport(source);
624
+ const cleanedLines = source.split("\n").filter((line) => !MANAGED_TAILWIND_IMPORTS.has(line.trim()));
625
+ let insertAt = -1;
626
+ for (let index = 0; index < cleanedLines.length; index += 1) {
627
+ if (cleanedLines[index].trim().startsWith("@import ")) {
628
+ insertAt = index;
629
+ continue;
630
+ }
631
+ if (insertAt !== -1 && cleanedLines[index].trim() !== "") break;
632
+ }
633
+ if (insertAt === -1) return compactCss([
634
+ nextImport,
635
+ "",
636
+ ...cleanedLines
637
+ ].join("\n"));
638
+ cleanedLines.splice(insertAt + 1, 0, nextImport);
639
+ return compactCss(cleanedLines.join("\n"));
640
+ }
641
+ function resolveManagedTailwindImport(source) {
642
+ if (source.includes(SHADCN_TAILWIND_IMPORT)) return SHADCN_TAILWIND_IMPORT;
643
+ if (source.includes(BEJAMAS_TAILWIND_IMPORT)) return BEJAMAS_TAILWIND_IMPORT;
644
+ return BEJAMAS_TAILWIND_IMPORT;
645
+ }
646
+ function normalizeSourceForComparison(source) {
647
+ return source.replace(/\r\n/g, "\n").replace(/\s+/g, " ").trim();
648
+ }
649
+ function upsertFrontmatterImport(source, importLine) {
650
+ if (source.includes(importLine)) return source;
651
+ const frontmatterEnd = source.indexOf("\n---", 3);
652
+ if (source.startsWith("---\n") && frontmatterEnd !== -1) return `${source.slice(0, frontmatterEnd).trimEnd()}\n${importLine}${source.slice(frontmatterEnd)}`;
653
+ return `${importLine}\n${source}`;
654
+ }
655
+ function getTemplateI18nVariant(filepath) {
656
+ return filepath.includes(`${path.sep}apps${path.sep}web${path.sep}`) ? "monorepo" : "astro";
657
+ }
658
+ function buildTemplateI18nSource(variant, language) {
659
+ return compactSource(TEMPLATE_I18N_SOURCES[variant].replace("export const CURRENT_LANGUAGE: TemplateLanguage = \"en\";", `export const CURRENT_LANGUAGE: TemplateLanguage = "${language}";`));
660
+ }
661
+ function transformTemplateLayoutToI18n(source) {
662
+ if (source.includes(TEMPLATE_APP_UI_IMPORT)) return source;
663
+ let next = upsertFrontmatterImport(source, TEMPLATE_APP_UI_IMPORT);
664
+ next = next.replace(/<html([^>]*?)\slang=(\"[^\"]*\"|\{[^}]+\})([^>]*)>/, "<html$1$3>").replace(/<html([^>]*?)\sdir=(\"[^\"]*\"|\{[^}]+\})([^>]*)>/, "<html$1$3>");
665
+ next = next.replace(/<html([^>]*)>/, "<html$1 lang={appUi.lang} dir={appUi.dir}>");
666
+ next = next.replace(/<title>[\s\S]*?<\/title>/, "<title>{appUi.metadataTitle}</title>");
667
+ return compactSource(next);
668
+ }
669
+ function transformStarterPageToI18n(source) {
670
+ if (source.includes(TEMPLATE_APP_UI_IMPORT)) return source;
671
+ const normalized = normalizeSourceForComparison(source);
672
+ const match = TEMPLATE_PAGE_VARIANTS.find((variant) => variant.matchers.every((matcher) => normalized.includes(normalizeSourceForComparison(matcher))));
673
+ if (!match) return source;
674
+ return compactSource(match.i18nSource);
675
+ }
676
+ function upsertThemeInlineFont(source, fontVariable) {
677
+ const pattern = /@theme inline\s*\{[\s\S]*?\n\}/m;
678
+ if (!pattern.test(source)) {
679
+ const block = `@theme inline {\n${[fontVariable ? ` ${fontVariable}: var(${fontVariable});` : null, " --font-heading: var(--font-heading);"].filter(Boolean).join("\n")}\n}`;
680
+ const customVariantIndex = source.indexOf("@custom-variant");
681
+ if (customVariantIndex !== -1) {
682
+ const nextLineIndex = source.indexOf("\n", customVariantIndex);
683
+ return `${source.slice(0, nextLineIndex + 1).trimEnd()}\n\n${block}\n\n${source.slice(nextLineIndex + 1).trimStart()}`;
684
+ }
685
+ return `${block}\n\n${source.trimStart()}`;
686
+ }
687
+ return source.replace(pattern, (block) => {
688
+ const withoutManagedFonts = block.replace(/\n\s*--font-(sans|serif|mono|heading):[^\n]+/g, "").replace(/\n\s*--bejamas-font-family:[^\n]+/g, "").replace(/@theme inline\s*\{\n?/, "@theme inline {\n");
689
+ const declarations = [fontVariable ? ` ${fontVariable}: var(${fontVariable});` : null, " --font-heading: var(--font-heading);"].filter(Boolean).join("\n");
690
+ return withoutManagedFonts.replace("@theme inline {\n", `@theme inline {\n${declarations}\n`);
691
+ });
692
+ }
693
+ function upsertBaseLayerHtmlFont(source, fontClass) {
694
+ const pattern = /@layer base\s*\{[\s\S]*?\n\}/m;
695
+ if (!pattern.test(source)) return `${source.trimEnd()}\n\n@layer base {\n .cn-font-heading {\n @apply font-heading;\n }\n html {\n @apply ${fontClass};\n }\n}\n`;
696
+ return source.replace(pattern, (block) => {
697
+ return block.replace(/\n\s*html\s*\{[\s\S]*?\n\s*\}/m, "").replace(/\n\s*\.cn-font-heading\s*\{[\s\S]*?\n\s*\}/m, "").replace(/\n\}$/, `\n .cn-font-heading {\n @apply font-heading;\n }\n html {\n @apply ${fontClass};\n }\n}`);
698
+ });
699
+ }
700
+ function transformDesignSystemCss(source, config, themeVars) {
701
+ const effectiveThemeVars = themeVars ?? buildRegistryTheme(config).cssVars;
702
+ const rootVars = {
703
+ ...Object.fromEntries(Object.entries(effectiveThemeVars.theme ?? {}).filter(([key]) => key !== "bejamas-font-family")),
704
+ ...effectiveThemeVars.light ?? {}
705
+ };
706
+ const darkVars = { ...effectiveThemeVars.dark ?? {} };
707
+ const font = getFontValue(config.font);
708
+ const tailwindImport = resolveManagedTailwindImport(source);
709
+ let next = stripLegacyCreateBlock(source);
710
+ next = upsertImports(next, [tailwindImport]);
711
+ next = replaceTopLevelBlock(next, ":root", buildCssVarBlock(":root", rootVars));
712
+ next = replaceTopLevelBlock(next, ".dark", buildCssVarBlock(".dark", darkVars));
713
+ next = upsertThemeInlineFont(next, font?.font.variable);
714
+ if (font) next = upsertBaseLayerHtmlFont(next, fontClassFromCssVariable(font.font.variable));
715
+ return compactCss(next);
716
+ }
717
+ function transformAstroManagedFontCss(source, fontVariable) {
718
+ const tailwindImport = resolveManagedTailwindImport(source);
719
+ let next = stripLegacyCreateBlock(source);
720
+ next = upsertImports(next, [tailwindImport]);
721
+ next = upsertThemeInlineFont(next, fontVariable);
722
+ if (fontVariable) next = upsertBaseLayerHtmlFont(next, fontClassFromCssVariable(fontVariable));
723
+ return compactCss(next);
724
+ }
725
+ function transformManagedTailwindImportCss(source) {
726
+ return upsertManagedTailwindImport(source);
727
+ }
728
+ async function patchComponentsJson(filepath, config) {
729
+ const current = await fs.readJson(filepath);
730
+ const { rsc: _rsc, tsx: _tsx,...normalizedCurrent } = current;
731
+ const next = {
732
+ ...normalizedCurrent,
733
+ $schema: BEJAMAS_COMPONENTS_SCHEMA_URL,
734
+ style: getStyleId(config.style),
735
+ iconLibrary: config.iconLibrary,
736
+ rtl: config.rtl,
737
+ tailwind: {
738
+ ...current.tailwind ?? {},
739
+ baseColor: config.baseColor,
740
+ cssVariables: true
741
+ }
742
+ };
743
+ await fs.writeJson(filepath, next, { spaces: 2 });
744
+ }
745
+ async function patchCssFileWithTheme(filepath, config, themeVars) {
746
+ const next = transformDesignSystemCss(await fs.readFile(filepath, "utf8"), config, themeVars);
747
+ await fs.writeFile(filepath, next, "utf8");
748
+ }
749
+ async function patchCssFileWithAstroFont(filepath, fontVariable) {
750
+ const next = transformAstroManagedFontCss(await fs.readFile(filepath, "utf8"), fontVariable);
751
+ await fs.writeFile(filepath, next, "utf8");
752
+ }
753
+ async function patchCssFileManagedTailwindImport(filepath) {
754
+ const next = transformManagedTailwindImportCss(await fs.readFile(filepath, "utf8"));
755
+ await fs.writeFile(filepath, next, "utf8");
756
+ }
757
+ async function syncManagedTailwindCss(projectPath) {
758
+ const componentJsonFiles = await fg("**/components.json", {
759
+ cwd: projectPath,
760
+ absolute: true,
761
+ ignore: ["**/node_modules/**"]
762
+ });
763
+ await Promise.all(componentJsonFiles.map(async (componentJsonPath) => {
764
+ const cssRelativePath = (await fs.readJson(componentJsonPath))?.tailwind?.css;
765
+ if (typeof cssRelativePath !== "string" || cssRelativePath.length === 0) return;
766
+ const cssPath = path.resolve(path.dirname(componentJsonPath), cssRelativePath);
767
+ if (!await fs.pathExists(cssPath)) return;
768
+ await patchCssFileManagedTailwindImport(cssPath);
769
+ }));
770
+ }
771
+ async function syncAstroManagedFontCss(projectPath, fontVariable) {
772
+ const componentJsonFiles = await fg("**/components.json", {
773
+ cwd: projectPath,
774
+ ignore: [
775
+ "**/node_modules/**",
776
+ "**/dist/**",
777
+ "**/.astro/**"
778
+ ]
779
+ });
780
+ const cssFiles = /* @__PURE__ */ new Set();
781
+ for (const relativePath of componentJsonFiles) {
782
+ const absolutePath = path.resolve(projectPath, relativePath);
783
+ const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
784
+ if (typeof cssPath === "string" && cssPath.length > 0) cssFiles.add(path.resolve(path.dirname(absolutePath), cssPath));
785
+ }
786
+ await Promise.all(Array.from(cssFiles).map((filepath) => patchCssFileWithAstroFont(filepath, fontVariable)));
787
+ }
788
+ async function patchTemplateI18nFile(filepath, config) {
789
+ if (!config.rtl) return;
790
+ const nextLanguage = getDocumentLanguage(config);
791
+ const nextSource = buildTemplateI18nSource(getTemplateI18nVariant(filepath), nextLanguage);
792
+ if (!await fs.pathExists(filepath)) {
793
+ await fs.ensureDir(path.dirname(filepath));
794
+ await fs.writeFile(filepath, nextSource, "utf8");
795
+ return;
796
+ }
797
+ const current = await fs.readFile(filepath, "utf8");
798
+ const next = current.includes("export const CURRENT_LANGUAGE") ? current.replace(/export const CURRENT_LANGUAGE: TemplateLanguage = "[^"]+";/, `export const CURRENT_LANGUAGE: TemplateLanguage = "${nextLanguage}";`) : nextSource;
799
+ if (next !== current) await fs.writeFile(filepath, next, "utf8");
800
+ }
801
+ async function patchLayoutFile(filepath, config) {
802
+ if (!await fs.pathExists(filepath)) return;
803
+ const current = await fs.readFile(filepath, "utf8");
804
+ if (current.includes("from \"@/i18n/ui\"")) return;
805
+ if (config.rtl) {
806
+ const next$1 = transformTemplateLayoutToI18n(current);
807
+ if (next$1 !== current) await fs.writeFile(filepath, next$1, "utf8");
808
+ return;
809
+ }
810
+ const nextLanguage = getDocumentLanguage(config);
811
+ const nextDirection = getDocumentDirection(config);
812
+ let next = current.replace(/<html([^>]*?)lang="[^"]*"([^>]*)>/, "<html$1$2>").replace(/<html([^>]*?)dir="[^"]*"([^>]*)>/, "<html$1$2>");
813
+ next = next.replace(/<html([^>]*)>/, `<html$1 lang="${nextLanguage}"${config.rtl ? ` dir="${nextDirection}"` : ""}>`);
814
+ if (next !== current) await fs.writeFile(filepath, next, "utf8");
815
+ }
816
+ async function patchStarterPageFile(filepath, config) {
817
+ if (!config.rtl || !await fs.pathExists(filepath)) return;
818
+ const current = await fs.readFile(filepath, "utf8");
819
+ const next = transformStarterPageToI18n(current);
820
+ if (next !== current) await fs.writeFile(filepath, next, "utf8");
821
+ }
822
+ async function applyDesignSystemToProject(projectPath, config, options = {}) {
823
+ const componentJsonFiles = await fg("**/components.json", {
824
+ cwd: projectPath,
825
+ ignore: [
826
+ "**/node_modules/**",
827
+ "**/dist/**",
828
+ "**/.astro/**"
829
+ ]
830
+ });
831
+ const cssFiles = /* @__PURE__ */ new Set();
832
+ for (const relativePath of componentJsonFiles) {
833
+ const absolutePath = path.resolve(projectPath, relativePath);
834
+ await patchComponentsJson(absolutePath, config);
835
+ const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
836
+ if (typeof cssPath === "string" && cssPath.length > 0) {
837
+ const absoluteCssPath = path.resolve(path.dirname(absolutePath), cssPath);
838
+ cssFiles.add(absoluteCssPath);
839
+ }
840
+ }
841
+ await Promise.all(Array.from(cssFiles).map((filepath) => patchCssFileWithTheme(filepath, config, options.themeVars)));
842
+ await Promise.all([path.resolve(projectPath, "src/i18n/ui.ts"), path.resolve(projectPath, "apps/web/src/i18n/ui.ts")].map((filepath) => patchTemplateI18nFile(filepath, config)));
843
+ await Promise.all([path.resolve(projectPath, "src/layouts/Layout.astro"), path.resolve(projectPath, "apps/web/src/layouts/Layout.astro")].map((filepath) => patchLayoutFile(filepath, config)));
844
+ await Promise.all([path.resolve(projectPath, "src/pages/index.astro"), path.resolve(projectPath, "apps/web/src/pages/index.astro")].map((filepath) => patchStarterPageFile(filepath, config)));
845
+ const managedFonts = [toManagedAstroFont(config.font), config.fontHeading !== "inherit" ? toManagedAstroFont(`font-heading-${config.fontHeading}`) : null].filter((font) => font !== null);
846
+ const managedFont = managedFonts.find((font) => font.cssVariable !== "--font-heading");
847
+ if (managedFonts.length > 0 && managedFont) {
848
+ await syncAstroFontsInProject(projectPath, managedFonts, managedFont.cssVariable);
849
+ await cleanupAstroFontPackages(projectPath);
850
+ }
851
+ }
852
+
853
+ //#endregion
854
+ //#region src/utils/icon-transform.ts
855
+ const FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---/;
856
+ function rewriteAstroIcons(content, iconLibrary) {
857
+ if (!(iconLibrary in ICON_LIBRARY_COLLECTIONS)) return content;
858
+ const library = iconLibrary;
859
+ const shouldRewriteLucideImports = library !== "lucide";
860
+ const frontmatterMatch = content.match(FRONTMATTER_PATTERN);
861
+ if (!frontmatterMatch) return content;
862
+ let frontmatter = frontmatterMatch[1];
863
+ let body = content.slice(frontmatterMatch[0].length);
864
+ const iconUsages = [];
865
+ const semanticIconImports = [];
866
+ if (shouldRewriteLucideImports) {
867
+ frontmatter = frontmatter.replace(/^\s*import\s+([A-Za-z0-9_$]+)\s+from\s+["']@lucide\/astro\/icons\/([^"']+)["'];?\s*$/gm, (full, localName, iconPath) => {
868
+ const semanticName = getSemanticIconNameFromLucidePath(iconPath);
869
+ if (!semanticName) return full;
870
+ iconUsages.push({
871
+ localName,
872
+ semanticName
873
+ });
874
+ return "";
875
+ });
876
+ frontmatter = frontmatter.replace(/import\s+\{([^}]*)\}\s+from\s+["']@lucide\/astro["'];?/g, (full, importsBlock) => {
877
+ const keptImports = [];
878
+ for (const rawImport of importsBlock.split(",")) {
879
+ const trimmedImport = rawImport.trim();
880
+ if (!trimmedImport) continue;
881
+ const aliasMatch = trimmedImport.match(/^([A-Za-z0-9_$]+)(?:\s+as\s+([A-Za-z0-9_$]+))?$/);
882
+ if (!aliasMatch) {
883
+ keptImports.push(trimmedImport);
884
+ continue;
885
+ }
886
+ const [, importedName, localAlias] = aliasMatch;
887
+ const semanticName = getSemanticIconNameFromLucideExport(importedName);
888
+ if (!semanticName) {
889
+ keptImports.push(trimmedImport);
890
+ continue;
891
+ }
892
+ iconUsages.push({
893
+ localName: localAlias ?? importedName,
894
+ semanticName
895
+ });
896
+ }
897
+ if (!keptImports.length) return "";
898
+ return `import { ${keptImports.join(", ")} } from "@lucide/astro";`;
899
+ });
900
+ }
901
+ frontmatter = frontmatter.replace(/^\s*import\s+([A-Za-z0-9_$]+)\s+from\s+["'][^"']*SemanticIcon\.astro["'];?\s*$/gm, (_full, localName) => {
902
+ semanticIconImports.push(localName);
903
+ return "";
904
+ });
905
+ if (!iconUsages.length && !semanticIconImports.length) return content;
906
+ for (const { localName, semanticName } of iconUsages.sort((left, right) => right.localName.length - left.localName.length)) {
907
+ const selfClosingPattern = new RegExp(`<${localName}(\\s[^>]*?)?\\s*/>`, "g");
908
+ const pairedPattern = new RegExp(`<${localName}(\\s[^>]*?)?>([\\s\\S]*?)<\\/${localName}>`, "g");
909
+ body = body.replace(selfClosingPattern, (_full, attributeString = "") => renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString));
910
+ body = body.replace(pairedPattern, (_full, attributeString = "") => renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString));
911
+ }
912
+ for (const localName of semanticIconImports.sort((left, right) => right.length - left.length)) {
913
+ const selfClosingPattern = new RegExp(`<${localName}(\\s[^>]*?)?\\s*/>`, "g");
914
+ const pairedPattern = new RegExp(`<${localName}(\\s[^>]*?)?>([\\s\\S]*?)<\\/${localName}>`, "g");
915
+ body = body.replace(selfClosingPattern, (full, attributeString = "") => renderSemanticIconUsage(full, library, attributeString));
916
+ body = body.replace(pairedPattern, (full, attributeString = "") => renderSemanticIconUsage(full, library, attributeString));
917
+ }
918
+ return `---\n${frontmatter.split("\n").filter((line, index, lines) => {
919
+ if (line.trim().length > 0) return true;
920
+ return lines[index - 1]?.trim().length > 0;
921
+ }).join("\n").trim()}\n---${body}`;
922
+ }
923
+ function renderSemanticIconUsage(original, library, attributeString = "") {
924
+ const semanticName = attributeString.match(/\sname=(["'])([^"']+)\1/)?.[2];
925
+ if (!semanticName || !SEMANTIC_ICON_NAMES.includes(semanticName)) return original;
926
+ return renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString.replace(/\sname=(["'])[^"']+\1/g, "").replace(/\slibrary=(["'])[^"']+\1/g, "").replace(/\sdata-icon-library=(["'])[^"']+\1/g, "").trim());
927
+ }
928
+
929
+ //#endregion
930
+ //#region src/utils/menu-transform.ts
931
+ const TRANSLUCENT_CLASSES = [
932
+ "animate-none!",
933
+ "relative",
934
+ "bg-popover/70",
935
+ "before:pointer-events-none",
936
+ "before:absolute",
937
+ "before:inset-0",
938
+ "before:-z-1",
939
+ "before:rounded-[inherit]",
940
+ "before:backdrop-blur-2xl",
941
+ "before:backdrop-saturate-150",
942
+ "**:data-[slot$=-item]:focus:bg-foreground/10",
943
+ "**:data-[slot$=-item]:data-highlighted:bg-foreground/10",
944
+ "**:data-[slot$=-separator]:bg-foreground/5",
945
+ "**:data-[slot$=-trigger]:focus:bg-foreground/10",
946
+ "**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10!",
947
+ "**:data-[variant=destructive]:focus:bg-foreground/10!",
948
+ "**:data-[variant=destructive]:text-accent-foreground!",
949
+ "**:data-[variant=destructive]:**:text-accent-foreground!"
950
+ ];
951
+ function transformMenuTokens(tokens, menuColor) {
952
+ const transformed = [];
953
+ for (const token of tokens) {
954
+ if (token === "cn-menu-target") {
955
+ if (menuColor === "inverted") transformed.push("dark");
956
+ else if (menuColor === "default-translucent") transformed.push(...TRANSLUCENT_CLASSES);
957
+ else if (menuColor === "inverted-translucent") transformed.push("dark", ...TRANSLUCENT_CLASSES);
958
+ continue;
959
+ }
960
+ if (token === "cn-menu-translucent") {
961
+ if (menuColor === "default-translucent" || menuColor === "inverted-translucent") transformed.push(...TRANSLUCENT_CLASSES);
962
+ continue;
963
+ }
964
+ transformed.push(token);
965
+ }
966
+ return Array.from(new Set(transformed));
967
+ }
968
+ function rewriteAstroMenus(content, menuColor) {
969
+ if (!content.includes("cn-menu-target") && !content.includes("cn-menu-translucent")) return content;
970
+ return content.replace(/(["'])([^"']*\bcn-menu-(?:target|translucent)\b[^"']*)\1/g, (_match, quote, classes) => {
971
+ return `${quote}${transformMenuTokens(String(classes).split(/\s+/).filter(Boolean), menuColor).join(" ")}${quote}`;
972
+ });
973
+ }
974
+
975
+ //#endregion
976
+ //#region src/utils/astro-imports.ts
977
+ function updateImportAliases(moduleSpecifier, config, isRemote = false) {
978
+ if (!moduleSpecifier.startsWith("@/") && !isRemote) return moduleSpecifier;
979
+ let specifier = moduleSpecifier;
980
+ if (isRemote && specifier.startsWith("@/")) specifier = specifier.replace(/^@\//, `@/registry/${config.style || "bejamas-juno"}/`);
981
+ if (!specifier.startsWith("@/registry/")) {
982
+ const alias = config.aliases.components.split("/")[0];
983
+ return specifier.replace(/^@\//, `${alias}/`);
984
+ }
985
+ if (specifier.match(/^@\/registry\/(.+)\/ui/)) return specifier.replace(/^@\/registry\/(.+)\/ui/, config.aliases.ui ?? `${config.aliases.components}/ui`);
986
+ if (config.aliases.components && specifier.match(/^@\/registry\/(.+)\/components/)) return specifier.replace(/^@\/registry\/(.+)\/components/, config.aliases.components);
987
+ if (config.aliases.lib && specifier.match(/^@\/registry\/(.+)\/lib/)) return specifier.replace(/^@\/registry\/(.+)\/lib/, config.aliases.lib);
988
+ if (config.aliases.hooks && specifier.match(/^@\/registry\/(.+)\/hooks/)) return specifier.replace(/^@\/registry\/(.+)\/hooks/, config.aliases.hooks);
989
+ return specifier.replace(/^@\/registry\/[^/]+/, config.aliases.components);
990
+ }
991
+ function rewriteAstroImports(content, config) {
992
+ let updated = content;
993
+ const utilsAlias = config.aliases?.utils;
994
+ const utilsImport = `${typeof utilsAlias === "string" && utilsAlias.includes("/") ? utilsAlias.split("/")[0] : "@"}/lib/utils`;
995
+ updated = updated.replace(/import\s+([\s\S]*?)\s+from\s+["']([^"']+)["']/g, (full, importsPart, specifier) => {
996
+ const next = updateImportAliases(specifier, config, false);
997
+ let finalSpec = next;
998
+ if (typeof importsPart === "string" && importsPart.split(/[{},\s]/).some((part) => part === "cn") && config.aliases.utils && (next === utilsImport || next === "@/lib/utils")) finalSpec = utilsImport === next ? next.replace(utilsImport, config.aliases.utils) : config.aliases.utils;
999
+ if (finalSpec === specifier) return full;
1000
+ return full.replace(specifier, finalSpec);
1001
+ });
1002
+ updated = updated.replace(/import\s+["']([^"']+)["']/g, (full, specifier) => {
1003
+ const next = updateImportAliases(specifier, config, false);
1004
+ if (next === specifier) return full;
1005
+ return full.replace(specifier, next);
1006
+ });
1007
+ updated = rewriteAstroMenus(updated, config.menuColor);
1008
+ return rewriteAstroIcons(updated, config.iconLibrary);
1009
+ }
1010
+ async function fixAstroImports(cwd, isVerbose) {
1011
+ const config = await getConfig(cwd);
1012
+ if (!config) return;
1013
+ const searchRoots = new Set([config.resolvedPaths.components, config.resolvedPaths.ui]);
1014
+ for (const root of Array.from(searchRoots)) {
1015
+ if (!root) continue;
1016
+ const astroFiles = await fg("**/*.astro", {
1017
+ cwd: root,
1018
+ absolute: true,
1019
+ dot: false
1020
+ });
1021
+ for (const filePath of astroFiles) {
1022
+ const original = await fs$1.readFile(filePath, "utf8");
1023
+ const rewritten = rewriteAstroImports(original, config);
1024
+ if (rewritten === original) continue;
1025
+ await fs$1.writeFile(filePath, rewritten, "utf8");
1026
+ if (isVerbose) logger.info(`[bejamas-ui] fixed imports in ${path.relative(cwd, filePath)}`);
1027
+ }
1028
+ }
44
1029
  }
45
1030
 
46
1031
  //#endregion
@@ -154,7 +1139,39 @@ const TEMPLATES = {
154
1139
  "astro-monorepo": "astro-monorepo",
155
1140
  "astro-with-component-docs-monorepo": "astro-with-component-docs-monorepo"
156
1141
  };
157
- const MONOREPO_TEMPLATE_URL = "https://codeload.github.com/bejamas/ui/tar.gz/main";
1142
+ const __filename = fileURLToPath(import.meta.url);
1143
+ const __dirname = path$1.dirname(__filename);
1144
+ function resolveLocalTemplatesDir() {
1145
+ for (const relativePath of [
1146
+ "../../../../templates",
1147
+ "../../../templates",
1148
+ "../../templates"
1149
+ ]) {
1150
+ const candidate = path$1.resolve(__dirname, relativePath);
1151
+ if (fs.existsSync(candidate)) return candidate;
1152
+ }
1153
+ return path$1.resolve(__dirname, "../../../../templates");
1154
+ }
1155
+ const LOCAL_TEMPLATES_DIR = resolveLocalTemplatesDir();
1156
+ async function applyLocalPackageOverrides(projectPath) {
1157
+ const bejamasPackageOverride = process.env.BEJAMAS_PACKAGE_OVERRIDE;
1158
+ if (!bejamasPackageOverride) return;
1159
+ const packageJsonPaths = await fg("**/package.json", {
1160
+ cwd: projectPath,
1161
+ absolute: true,
1162
+ ignore: ["**/node_modules/**"]
1163
+ });
1164
+ const normalizedOverride = bejamasPackageOverride.replace(/\\/g, "/");
1165
+ await Promise.all(packageJsonPaths.map(async (packageJsonPath) => {
1166
+ const packageJson = await fs.readJson(packageJsonPath);
1167
+ let changed = false;
1168
+ for (const field of ["dependencies", "devDependencies"]) if (packageJson[field]?.bejamas) {
1169
+ packageJson[field].bejamas = `file:${normalizedOverride}`;
1170
+ changed = true;
1171
+ }
1172
+ if (changed) await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
1173
+ }));
1174
+ }
158
1175
  async function createProject(options) {
159
1176
  options = {
160
1177
  srcDir: false,
@@ -207,7 +1224,7 @@ async function createProject(options) {
207
1224
  logger.break();
208
1225
  process.exit(1);
209
1226
  }
210
- if (fs.existsSync(path.resolve(options.cwd, projectName, "package.json"))) {
1227
+ if (fs.existsSync(path$1.resolve(options.cwd, projectName, "package.json"))) {
211
1228
  logger.break();
212
1229
  logger.error(`A project with the name ${highlighter.info(projectName)} already exists.`);
213
1230
  logger.error(`Please choose a different name and try again.`);
@@ -227,39 +1244,22 @@ async function createProject(options) {
227
1244
  }
228
1245
  async function createProjectFromTemplate(projectPath, options) {
229
1246
  const createSpinner = spinner(`Creating a new project from template. This may take a few minutes.`).start();
230
- const TEMPLATE_TAR_SUBPATH = {
231
- astro: "ui-main/templates/astro",
232
- "astro-monorepo": "ui-main/templates/monorepo-astro",
233
- "astro-with-component-docs-monorepo": "ui-main/templates/monorepo-astro-with-docs"
1247
+ const TEMPLATE_DIRNAME = {
1248
+ astro: "astro",
1249
+ "astro-monorepo": "monorepo-astro",
1250
+ "astro-with-component-docs-monorepo": "monorepo-astro-with-docs"
234
1251
  };
235
1252
  try {
236
1253
  dotenv.config({ quiet: true });
237
- const templatePath = path.join(os.tmpdir(), `bejamas-template-${Date.now()}`);
238
- await fs.ensureDir(templatePath);
239
- const authToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
240
- const usedAuth = Boolean(authToken);
241
- const headers = { "User-Agent": "bejamas-cli" };
242
- if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
243
- const response = await fetch(MONOREPO_TEMPLATE_URL, { headers });
244
- if (!response.ok) {
245
- if (response.status === 401 || response.status === 403 || !usedAuth && response.status === 404) throw new Error("Unauthorized to access private template. Set GITHUB_TOKEN or GH_TOKEN (in .env or env) with repo access and try again.");
246
- if (response.status === 404) throw new Error("Failed to download template: not found.");
247
- throw new Error(`Failed to download template: ${response.status} ${response.statusText}`);
248
- }
249
- const tarPath = path.resolve(templatePath, "template.tar.gz");
250
- await fs.writeFile(tarPath, Buffer.from(await response.arrayBuffer()));
251
- const tarSubpath = TEMPLATE_TAR_SUBPATH[options.templateKey];
252
- const leafName = tarSubpath.split("/").pop();
253
- await execa("tar", [
254
- "-xzf",
255
- tarPath,
256
- "-C",
257
- templatePath,
258
- "--strip-components=2",
259
- tarSubpath
260
- ]);
261
- const extractedPath = path.resolve(templatePath, leafName);
262
- await fs.move(extractedPath, projectPath);
1254
+ const templatePath = path$1.join(os.tmpdir(), `bejamas-template-${Date.now()}`);
1255
+ const templateSource = path$1.resolve(LOCAL_TEMPLATES_DIR, TEMPLATE_DIRNAME[options.templateKey]);
1256
+ if (!await fs.pathExists(templateSource)) throw new Error(`Local template not found: ${templateSource}`);
1257
+ await fs.copy(templateSource, projectPath, { filter: (source) => {
1258
+ const basename = path$1.basename(source);
1259
+ return basename !== "node_modules" && basename !== ".astro";
1260
+ } });
1261
+ await removeEmptyTemplateI18nDirs(projectPath);
1262
+ await applyLocalPackageOverrides(projectPath);
263
1263
  await fs.remove(templatePath);
264
1264
  await execa(options.packageManager, ["install"], { cwd: projectPath });
265
1265
  try {
@@ -273,51 +1273,295 @@ async function createProjectFromTemplate(projectPath, options) {
273
1273
  "Initial commit"
274
1274
  ], { cwd: projectPath });
275
1275
  }
276
- } catch (_) {}
277
- createSpinner?.succeed("Creating a new project from template.");
278
- } catch (error) {
279
- createSpinner?.fail("Something went wrong creating a new project from template.");
280
- handleError(error);
1276
+ } catch (_) {}
1277
+ createSpinner?.succeed("Creating a new project from template.");
1278
+ } catch (error) {
1279
+ createSpinner?.fail("Something went wrong creating a new project from template.");
1280
+ handleError(error);
1281
+ }
1282
+ }
1283
+ async function removeEmptyTemplateI18nDirs(projectPath) {
1284
+ const candidates = [path$1.resolve(projectPath, "src/i18n"), path$1.resolve(projectPath, "apps/web/src/i18n")];
1285
+ await Promise.all(candidates.map(async (candidate) => {
1286
+ if (!await fs.pathExists(candidate)) return;
1287
+ if ((await fs.readdir(candidate)).length === 0) await fs.remove(candidate);
1288
+ }));
1289
+ }
1290
+
1291
+ //#endregion
1292
+ //#region src/utils/installed-ui-components.ts
1293
+ function toRegistryName(filename) {
1294
+ return filename.replace(/\.[^.]+$/, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1295
+ }
1296
+ async function pathExists$1(filepath) {
1297
+ try {
1298
+ await promises.access(filepath);
1299
+ return true;
1300
+ } catch {
1301
+ return false;
1302
+ }
1303
+ }
1304
+ async function detectInstalledUiComponents(uiDir) {
1305
+ if (!await pathExists$1(uiDir)) return [];
1306
+ const entries = await promises.readdir(uiDir, { withFileTypes: true });
1307
+ const components = /* @__PURE__ */ new Set();
1308
+ for (const entry of entries) {
1309
+ if (entry.name.startsWith(".")) continue;
1310
+ const entryPath = path.resolve(uiDir, entry.name);
1311
+ if (entry.isDirectory()) {
1312
+ if ((await promises.readdir(entryPath, { withFileTypes: true })).some((child) => child.isFile() && (/\.(astro|tsx|jsx)$/.test(child.name) || /^(index\.(ts|js|tsx|jsx))$/.test(child.name)))) components.add(entry.name);
1313
+ continue;
1314
+ }
1315
+ if (entry.isFile() && /\.(astro|tsx|jsx)$/.test(entry.name) && !entry.name.startsWith("index.")) components.add(toRegistryName(entry.name));
1316
+ }
1317
+ return Array.from(components).sort();
1318
+ }
1319
+ async function getInstalledUiComponents(cwd) {
1320
+ const config = await getConfig(cwd);
1321
+ if (!config) return [];
1322
+ return detectInstalledUiComponents(config.resolvedPaths.ui);
1323
+ }
1324
+
1325
+ //#endregion
1326
+ //#region src/utils/reorganize-components.ts
1327
+ async function fetchRegistryItem(componentName, registryUrl, style = "bejamas-juno") {
1328
+ const url = `${registryUrl}/styles/${style}/${componentName}.json`;
1329
+ try {
1330
+ const response = await fetch(url);
1331
+ if (!response.ok) {
1332
+ const fallbackUrl = `${registryUrl}/${componentName}.json`;
1333
+ const fallbackResponse = await fetch(fallbackUrl);
1334
+ if (!fallbackResponse.ok) return null;
1335
+ return await fallbackResponse.json();
1336
+ }
1337
+ return await response.json();
1338
+ } catch {
1339
+ return null;
1340
+ }
1341
+ }
1342
+ function getSubfolderFromPaths(files) {
1343
+ if (!files || files.length === 0) return null;
1344
+ const uiFiles = files.filter((file) => file.type === "registry:ui");
1345
+ if (uiFiles.length < 2) return null;
1346
+ const subfolders = /* @__PURE__ */ new Set();
1347
+ for (const file of uiFiles) {
1348
+ const parts = file.path.split("/");
1349
+ const uiIndex = parts.indexOf("ui");
1350
+ if (uiIndex !== -1 && parts.length > uiIndex + 2) subfolders.add(parts[uiIndex + 1]);
1351
+ }
1352
+ if (subfolders.size === 1) return Array.from(subfolders)[0];
1353
+ if (uiFiles.length > 0) {
1354
+ const dirname$1 = path.dirname(uiFiles[0].path);
1355
+ const folderName = path.basename(dirname$1);
1356
+ if (folderName && folderName !== "ui") return folderName;
1357
+ }
1358
+ return null;
1359
+ }
1360
+ async function pathExists(filePath) {
1361
+ try {
1362
+ await fs$1.access(filePath);
1363
+ return true;
1364
+ } catch {
1365
+ return false;
1366
+ }
1367
+ }
1368
+ function resolveShadcnUiRelativePath(filePath, uiDir) {
1369
+ const normalizedFilePath = filePath.replace(/^\/|\/$/g, "");
1370
+ const lastTargetSegment = path.basename(uiDir.replace(/^\/|\/$/g, ""));
1371
+ if (!lastTargetSegment) return path.basename(normalizedFilePath);
1372
+ const fileSegments = normalizedFilePath.split("/");
1373
+ const commonDirIndex = fileSegments.findIndex((segment) => segment === lastTargetSegment);
1374
+ if (commonDirIndex === -1) return fileSegments[fileSegments.length - 1];
1375
+ return fileSegments.slice(commonDirIndex + 1).join("/");
1376
+ }
1377
+ /**
1378
+ * Current upstream shadcn workspace installs flatten `ui/foo/Bar.astro` when
1379
+ * the resolved ui target ends in a segment like `components` instead of `ui`.
1380
+ * We keep reorganization only for that compatibility case.
1381
+ */
1382
+ function shouldReorganizeRegistryUiFiles(files, uiDir) {
1383
+ if (!uiDir) return false;
1384
+ if (!getSubfolderFromPaths(files)) return false;
1385
+ return files.filter((file) => file.type === "registry:ui").some((file) => {
1386
+ return !resolveShadcnUiRelativePath(file.path, uiDir).includes("/");
1387
+ });
1388
+ }
1389
+ /**
1390
+ * Reorganizes one multi-file registry:ui item from flat output into its
1391
+ * expected subfolder. This is the filesystem-level compatibility shim.
1392
+ */
1393
+ async function reorganizeRegistryUiFiles(files, uiDir, verbose, overwriteExisting = false) {
1394
+ const result = {
1395
+ totalMoved: 0,
1396
+ movedFiles: [],
1397
+ skippedFiles: []
1398
+ };
1399
+ if (!uiDir || !files || files.length === 0) return result;
1400
+ const subfolder = getSubfolderFromPaths(files);
1401
+ if (!subfolder) return result;
1402
+ const uiFiles = files.filter((file) => file.type === "registry:ui");
1403
+ const targetDir = path.join(uiDir, subfolder);
1404
+ for (const file of uiFiles) {
1405
+ const filename = path.basename(file.path);
1406
+ const flatPath = path.join(uiDir, filename);
1407
+ const targetPath = path.join(targetDir, filename);
1408
+ if (!await pathExists(flatPath)) continue;
1409
+ if (await pathExists(targetPath)) {
1410
+ if (overwriteExisting) {
1411
+ await fs$1.mkdir(targetDir, { recursive: true });
1412
+ await fs$1.unlink(targetPath);
1413
+ await fs$1.rename(flatPath, targetPath);
1414
+ result.totalMoved++;
1415
+ result.movedFiles.push(`${subfolder}/${filename}`);
1416
+ if (verbose) logger.info(`[bejamas-ui] Replaced ${subfolder}/${filename} with the reinstalled version`);
1417
+ continue;
1418
+ }
1419
+ try {
1420
+ await fs$1.unlink(flatPath);
1421
+ result.skippedFiles.push(`${subfolder}/${filename}`);
1422
+ if (verbose) logger.info(`[bejamas-ui] Removed flat duplicate: ${filename} (${subfolder}/${filename} exists)`);
1423
+ } catch {
1424
+ result.skippedFiles.push(`${subfolder}/${filename}`);
1425
+ }
1426
+ continue;
1427
+ }
1428
+ await fs$1.mkdir(targetDir, { recursive: true });
1429
+ await fs$1.rename(flatPath, targetPath);
1430
+ result.totalMoved++;
1431
+ result.movedFiles.push(`${subfolder}/${filename}`);
1432
+ if (verbose) logger.info(`[bejamas-ui] Moved ${filename} -> ${subfolder}/${filename}`);
1433
+ }
1434
+ return result;
1435
+ }
1436
+ async function reorganizeComponents(components, uiDir, registryUrl, verbose, style = "bejamas-juno", overwriteExisting = false) {
1437
+ const result = {
1438
+ totalMoved: 0,
1439
+ movedFiles: [],
1440
+ skippedFiles: []
1441
+ };
1442
+ if (!uiDir || components.length === 0) return result;
1443
+ for (const componentName of components) try {
1444
+ const registryItem = await fetchRegistryItem(componentName, registryUrl, style);
1445
+ if (!registryItem) {
1446
+ if (verbose) logger.info(`[bejamas-ui] Could not fetch registry for ${componentName}, skipping reorganization`);
1447
+ continue;
1448
+ }
1449
+ if (!shouldReorganizeRegistryUiFiles(registryItem.files, uiDir)) continue;
1450
+ const componentResult = await reorganizeRegistryUiFiles(registryItem.files, uiDir, verbose, overwriteExisting);
1451
+ result.totalMoved += componentResult.totalMoved;
1452
+ result.movedFiles.push(...componentResult.movedFiles);
1453
+ result.skippedFiles.push(...componentResult.skippedFiles);
1454
+ if (componentResult.totalMoved > 0 && verbose) {
1455
+ const subfolder = getSubfolderFromPaths(registryItem.files);
1456
+ logger.info(`[bejamas-ui] Reorganized ${componentName} into ${subfolder}/`);
1457
+ }
1458
+ } catch (err) {
1459
+ if (verbose) logger.warn(`[bejamas-ui] Failed to reorganize ${componentName}: ${err}`);
281
1460
  }
1461
+ return result;
282
1462
  }
283
1463
 
284
1464
  //#endregion
285
1465
  //#region src/utils/shadcn-cli.ts
286
- const require = createRequire(import.meta.url);
287
- const PINNED_SHADCN_VERSION = "3.8.5";
1466
+ const PINNED_SHADCN_VERSION = "4.1.1";
288
1467
  const PINNED_SHADCN_PACKAGE = `shadcn@${PINNED_SHADCN_VERSION}`;
289
- function resolveBundledShadcnEntrypoint() {
290
- try {
291
- return require.resolve("shadcn");
292
- } catch {
293
- return null;
294
- }
1468
+ const PINNED_SHADCN_EXEC_PREFIX = path.join(os$1.tmpdir(), "bejamas-shadcn", PINNED_SHADCN_VERSION);
1469
+ async function ensurePinnedShadcnExecPrefix() {
1470
+ await fs$1.mkdir(PINNED_SHADCN_EXEC_PREFIX, { recursive: true });
1471
+ return PINNED_SHADCN_EXEC_PREFIX;
295
1472
  }
296
- function buildPinnedShadcnInvocation(shadcnArgs, bundledEntrypoint = resolveBundledShadcnEntrypoint()) {
297
- if (bundledEntrypoint) return {
298
- cmd: process.execPath,
299
- args: [bundledEntrypoint, ...shadcnArgs],
300
- source: "bundled"
301
- };
1473
+ function getNpmExecutable() {
1474
+ return process.platform === "win32" ? "npm.cmd" : "npm";
1475
+ }
1476
+ function buildPinnedShadcnInvocation(shadcnArgs) {
302
1477
  return {
303
- cmd: "npx",
1478
+ cmd: getNpmExecutable(),
304
1479
  args: [
305
- "-y",
306
- PINNED_SHADCN_PACKAGE,
1480
+ "exec",
1481
+ "--yes",
1482
+ "--prefix",
1483
+ PINNED_SHADCN_EXEC_PREFIX,
1484
+ `--package=${PINNED_SHADCN_PACKAGE}`,
1485
+ "--",
1486
+ "shadcn",
307
1487
  ...shadcnArgs
308
1488
  ],
309
- source: "fallback"
1489
+ source: "isolated"
310
1490
  };
311
1491
  }
312
1492
 
1493
+ //#endregion
1494
+ //#region src/utils/shadcn-command.ts
1495
+ function extractPassthroughArgs(rawArgv, commandName) {
1496
+ const commandIndex = rawArgv.findIndex((arg) => arg === commandName);
1497
+ if (commandIndex === -1) return [];
1498
+ const rest = rawArgv.slice(commandIndex + 1);
1499
+ const doubleDashIndex = rest.indexOf("--");
1500
+ if (doubleDashIndex === -1) return [];
1501
+ return rest.slice(doubleDashIndex + 1);
1502
+ }
1503
+ async function runShadcnCommand({ cwd, args, env }) {
1504
+ await ensurePinnedShadcnExecPrefix();
1505
+ const invocation = buildPinnedShadcnInvocation(args);
1506
+ await execa(invocation.cmd, invocation.args, {
1507
+ cwd,
1508
+ env: {
1509
+ ...process.env,
1510
+ ...env
1511
+ },
1512
+ stdio: "inherit"
1513
+ });
1514
+ }
1515
+
313
1516
  //#endregion
314
1517
  //#region src/commands/init.ts
315
- const DEFAULT_REGISTRY_URL$1 = "https://ui.bejamas.com/r";
316
- const DEFAULT_COMPONENTS_BASE_COLOR = "neutral";
1518
+ function resolveDesignSystemConfig(options) {
1519
+ const rtlLanguage = options.rtl && options.lang ? options.lang : DEFAULT_DESIGN_SYSTEM_CONFIG.rtlLanguage;
1520
+ if (options.preset && isPresetCode(options.preset)) {
1521
+ const decoded = decodePreset(options.preset);
1522
+ if (decoded) return normalizeDesignSystemConfig(designSystemConfigSchema.parse({
1523
+ ...DEFAULT_DESIGN_SYSTEM_CONFIG,
1524
+ ...decoded,
1525
+ template: options.template ?? DEFAULT_DESIGN_SYSTEM_CONFIG.template,
1526
+ rtl: options.rtl ?? false,
1527
+ rtlLanguage
1528
+ }));
1529
+ }
1530
+ const baseColor = options.baseColor ?? DEFAULT_DESIGN_SYSTEM_CONFIG.baseColor;
1531
+ return normalizeDesignSystemConfig(designSystemConfigSchema.parse({
1532
+ ...DEFAULT_DESIGN_SYSTEM_CONFIG,
1533
+ template: options.template ?? DEFAULT_DESIGN_SYSTEM_CONFIG.template,
1534
+ rtl: options.rtl ?? false,
1535
+ rtlLanguage,
1536
+ baseColor,
1537
+ theme: baseColor === DEFAULT_DESIGN_SYSTEM_CONFIG.baseColor ? DEFAULT_DESIGN_SYSTEM_CONFIG.theme : baseColor
1538
+ }));
1539
+ }
1540
+ function buildInitUrl(config, themeRef, env = process.env) {
1541
+ const params = new URLSearchParams({
1542
+ preset: encodePreset(config),
1543
+ template: config.template
1544
+ });
1545
+ if (config.rtl) {
1546
+ params.set("rtl", "true");
1547
+ params.set("lang", config.rtlLanguage);
1548
+ }
1549
+ if (themeRef) params.set("themeRef", themeRef);
1550
+ return `${buildUiUrl("/init", env)}?${params.toString()}`;
1551
+ }
1552
+ function buildThemeVarsFromRegistryItem(item) {
1553
+ return item?.cssVars ?? null;
1554
+ }
1555
+ async function fetchInitThemeVars(initUrl) {
1556
+ const response = await fetch(initUrl);
1557
+ if (!response.ok) return null;
1558
+ return buildThemeVarsFromRegistryItem(await response.json());
1559
+ }
317
1560
  const initOptionsSchema = z.object({
318
1561
  cwd: z.string(),
319
1562
  components: z.array(z.string()).optional(),
320
1563
  yes: z.boolean(),
1564
+ reinstall: z.boolean().optional(),
321
1565
  defaults: z.boolean(),
322
1566
  force: z.boolean(),
323
1567
  silent: z.boolean(),
@@ -327,20 +1571,98 @@ const initOptionsSchema = z.object({
327
1571
  template: z.string().optional().refine((val) => {
328
1572
  if (val) return TEMPLATES[val];
329
1573
  return true;
330
- }, { message: "Invalid template. Please use 'next' or 'next-monorepo'." }),
331
- baseColor: z.string().optional(),
332
- baseStyle: z.boolean()
1574
+ }, { message: "Invalid template. Please use 'astro', 'astro-monorepo', or 'astro-with-component-docs-monorepo'." }),
1575
+ preset: z.string().optional(),
1576
+ baseColor: z.string().optional().refine((val) => {
1577
+ if (val) return BASE_COLORS.find((color) => color.name === val);
1578
+ return true;
1579
+ }, { message: `Invalid base color. Please use '${BASE_COLORS.map((color) => color.name).join("', '")}'` }),
1580
+ baseStyle: z.boolean(),
1581
+ rtl: z.boolean().default(false),
1582
+ lang: z.enum(RTL_LANGUAGE_VALUES).optional(),
1583
+ themeRef: z.string().optional()
333
1584
  });
334
- function buildShadcnInitArgs(baseColor = DEFAULT_COMPONENTS_BASE_COLOR) {
1585
+ function shouldReinstallExistingComponents(options) {
1586
+ return options.reinstall ?? Boolean(options.preset);
1587
+ }
1588
+ function ensureShadcnReinstallFlag(forwardedOptions, shouldReinstall) {
1589
+ if (!shouldReinstall || forwardedOptions.includes("--reinstall") || forwardedOptions.includes("--no-reinstall")) return forwardedOptions;
1590
+ const passthroughIndex = forwardedOptions.indexOf("--");
1591
+ if (passthroughIndex === -1) return [...forwardedOptions, "--reinstall"];
335
1592
  return [
336
- "init",
337
- "--base-color",
338
- baseColor
1593
+ ...forwardedOptions.slice(0, passthroughIndex),
1594
+ "--reinstall",
1595
+ ...forwardedOptions.slice(passthroughIndex)
339
1596
  ];
340
1597
  }
341
- const init = new Command().name("init").description("initialize your project and install dependencies").argument("[components...]", "names, url or local path to component").option("-t, --template <template>", "the template to use. (next, next-monorepo)").option("-b, --base-color <base-color>", "the base color to use. (neutral, gray, zinc, stone, slate)", void 0).option("-y, --yes", "skip confirmation prompt.", true).option("-d, --defaults,", "use default configuration.", false).option("-f, --force", "force overwrite of existing configuration.", false).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-s, --silent", "mute output.", false).option("--src-dir", "use the src directory when creating a new project.", false).option("--no-src-dir", "do not use the src directory when creating a new project.").option("--css-variables", "use css variables for theming.", true).option("--no-css-variables", "do not use css variables for theming.").option("--no-base-style", "do not install the base shadcn style.").action(async (_components, opts) => {
1598
+ function extractOptionsForShadcnInit(rawArgv, cmd) {
1599
+ if (typeof cmd.getOptionValueSource === "function") {
1600
+ const opts = cmd.optsWithGlobals();
1601
+ const forwarded$1 = [];
1602
+ const getSource = (key) => cmd.getOptionValueSource(key);
1603
+ const addBoolean = (key, flag, negateFlag) => {
1604
+ if (getSource(key) !== "cli") return;
1605
+ const value = opts[key];
1606
+ if (typeof value !== "boolean") return;
1607
+ if (value) forwarded$1.push(flag);
1608
+ else if (negateFlag) forwarded$1.push(negateFlag);
1609
+ };
1610
+ addBoolean("yes", "--yes");
1611
+ addBoolean("force", "--force");
1612
+ addBoolean("silent", "--silent");
1613
+ addBoolean("reinstall", "--reinstall", "--no-reinstall");
1614
+ const initIndex$1 = rawArgv.findIndex((arg) => arg === "init");
1615
+ if (initIndex$1 !== -1) {
1616
+ const rest$1 = rawArgv.slice(initIndex$1 + 1);
1617
+ const doubleDashIndex = rest$1.indexOf("--");
1618
+ if (doubleDashIndex !== -1) forwarded$1.push(...rest$1.slice(doubleDashIndex));
1619
+ }
1620
+ return forwarded$1;
1621
+ }
1622
+ const initIndex = rawArgv.findIndex((arg) => arg === "init");
1623
+ if (initIndex === -1) return [];
1624
+ const rest = rawArgv.slice(initIndex + 1);
1625
+ const forwarded = [];
1626
+ const filteredFlags = new Set([
1627
+ "-v",
1628
+ "--verbose",
1629
+ "-t",
1630
+ "--template",
1631
+ "-b",
1632
+ "--base-color",
1633
+ "-p",
1634
+ "--preset",
1635
+ "--theme-ref",
1636
+ "-c",
1637
+ "--cwd",
1638
+ "-d",
1639
+ "--defaults",
1640
+ "--src-dir",
1641
+ "--no-src-dir",
1642
+ "--css-variables",
1643
+ "--no-css-variables",
1644
+ "--no-base-style",
1645
+ "--rtl",
1646
+ "--lang"
1647
+ ]);
1648
+ for (let index = 0; index < rest.length; index += 1) {
1649
+ const token = rest[index];
1650
+ if (token === "--") {
1651
+ forwarded.push("--", ...rest.slice(index + 1));
1652
+ break;
1653
+ }
1654
+ if (!token.startsWith("-")) continue;
1655
+ if (filteredFlags.has(token)) continue;
1656
+ forwarded.push(token);
1657
+ }
1658
+ return forwarded;
1659
+ }
1660
+ const init = new Command().name("init").description("initialize your project and install dependencies").argument("[components...]", "names, url or local path to component").option("-t, --template <template>", "the template to use. (astro, astro-monorepo, astro-with-component-docs-monorepo)").option("-b, --base-color <base-color>", "the base color to use. (neutral, gray, zinc, stone, slate)", void 0).option("-p, --preset <preset>", "the encoded create preset to use").option("--theme-ref <theme-ref>", "the custom theme ref to use").option("-y, --yes", "skip confirmation prompt.", false).option("-d, --defaults", "use default configuration.", false).option("-f, --force", "force overwrite of existing configuration.", false).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-s, --silent", "mute output.", false).option("--src-dir", "use the src directory when creating a new project.", false).option("--no-src-dir", "do not use the src directory when creating a new project.").option("--css-variables", "use css variables for theming.", true).option("--no-css-variables", "do not use css variables for theming.").option("--no-base-style", "do not install the base shadcn style.").option("--rtl", "enable right-to-left output", false).option("--lang <lang>", "set the RTL language. (ar, fa, he)").option("--reinstall", "re-install existing UI components. Enabled by default for preset switching.").option("--no-reinstall", "do not re-install existing UI components during preset switching.").action(async (_components, opts, cmd) => {
342
1661
  try {
343
- await runInit(opts);
1662
+ await runInit({
1663
+ ...opts,
1664
+ forwardedOptions: extractOptionsForShadcnInit(process.argv.slice(2), cmd)
1665
+ });
344
1666
  } catch (error) {
345
1667
  logger.break();
346
1668
  handleError(error);
@@ -349,6 +1671,7 @@ const init = new Command().name("init").description("initialize your project and
349
1671
  }
350
1672
  });
351
1673
  async function runInit(options) {
1674
+ const designConfig = resolveDesignSystemConfig(options);
352
1675
  let newProjectTemplate;
353
1676
  if (!options.skipPreflight) {
354
1677
  if ((await preFlightInit(options)).errors[MISSING_DIR_OR_EMPTY_PROJECT]) {
@@ -360,30 +1683,86 @@ async function runInit(options) {
360
1683
  }
361
1684
  }
362
1685
  if (newProjectTemplate) {
363
- options.cwd = path.resolve(options.cwd, {
1686
+ const projectPath = {
364
1687
  "astro-monorepo": "apps/web",
365
1688
  "astro-with-component-docs-monorepo": "apps/web",
366
1689
  astro: ""
367
- }[newProjectTemplate]);
1690
+ };
1691
+ await applyDesignSystemToProject(options.cwd, {
1692
+ ...designConfig,
1693
+ template: newProjectTemplate
1694
+ }, { themeVars: options.themeRef ? await fetchInitThemeVars(buildInitUrl({
1695
+ ...designConfig,
1696
+ template: newProjectTemplate
1697
+ }, options.themeRef)) ?? void 0 : void 0 });
1698
+ options.cwd = path.resolve(options.cwd, projectPath[newProjectTemplate]);
368
1699
  logger.log(`${highlighter.success("Success!")} Project initialization completed.\nYou may now add components.`);
369
1700
  return await getConfig(options.cwd);
370
1701
  }
371
1702
  try {
372
1703
  const env = {
373
1704
  ...process.env,
374
- REGISTRY_URL: process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL$1
1705
+ REGISTRY_URL: resolveRegistryUrl()
375
1706
  };
376
- const invocation = buildPinnedShadcnInvocation(buildShadcnInitArgs(options.baseColor ?? DEFAULT_COMPONENTS_BASE_COLOR));
377
- await execa(invocation.cmd, invocation.args, {
378
- stdio: "inherit",
1707
+ const initUrl = buildInitUrl(designConfig, options.themeRef);
1708
+ const shouldReinstall = shouldReinstallExistingComponents(options);
1709
+ const reinstallComponents = shouldReinstall ? await getInstalledUiComponents(options.cwd) : [];
1710
+ const forwardedOptions = ensureShadcnReinstallFlag(options.forwardedOptions ?? [], shouldReinstall);
1711
+ await runShadcnCommand({
379
1712
  cwd: options.cwd,
1713
+ args: [
1714
+ "init",
1715
+ initUrl,
1716
+ ...reinstallComponents,
1717
+ ...forwardedOptions
1718
+ ],
380
1719
  env
381
1720
  });
382
- } catch (err) {
1721
+ await syncManagedTailwindCss(options.cwd);
1722
+ const managedFonts = [toManagedAstroFont(designConfig.font), designConfig.fontHeading !== "inherit" ? toManagedAstroFont(`font-heading-${designConfig.fontHeading}`) : null].filter((font) => font !== null);
1723
+ const managedFont = managedFonts.find((font) => font.cssVariable !== "--font-heading");
1724
+ if (managedFonts.length > 0 && managedFont) {
1725
+ await syncAstroFontsInProject(options.cwd, managedFonts, managedFont.cssVariable);
1726
+ await syncAstroManagedFontCss(options.cwd, managedFont.cssVariable);
1727
+ await cleanupAstroFontPackages(options.cwd);
1728
+ }
1729
+ if (reinstallComponents.length > 0) {
1730
+ const config = await getConfig(options.cwd);
1731
+ const uiDir = config?.resolvedPaths.ui ?? "";
1732
+ const activeStyle = config?.style ?? "bejamas-juno";
1733
+ if (uiDir) await reorganizeComponents(reinstallComponents, uiDir, resolveRegistryUrl(), false, activeStyle, true);
1734
+ await fixAstroImports(options.cwd, false);
1735
+ }
1736
+ } catch {
383
1737
  process.exit(1);
384
1738
  }
385
1739
  }
386
1740
 
1741
+ //#endregion
1742
+ //#region src/commands/docs.ts
1743
+ const docs = new Command().name("docs").description("proxy to shadcn docs").argument("<components...>", "component names").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-b, --base <base>", "the base to use either 'base' or 'radix'. defaults to project base.").option("--json", "output as JSON.", false).action(async (components, opts) => {
1744
+ const cwd = path.resolve(opts.cwd ?? process.cwd());
1745
+ const rawArgv = process.argv.slice(2);
1746
+ const args = [
1747
+ "docs",
1748
+ ...components,
1749
+ "--cwd",
1750
+ cwd
1751
+ ];
1752
+ if (opts.base) args.push("--base", opts.base);
1753
+ if (opts.json) args.push("--json");
1754
+ const passthroughArgs = extractPassthroughArgs(rawArgv, "docs");
1755
+ if (passthroughArgs.length > 0) args.push(...passthroughArgs);
1756
+ try {
1757
+ await runShadcnCommand({
1758
+ cwd,
1759
+ args
1760
+ });
1761
+ } catch {
1762
+ process.exit(1);
1763
+ }
1764
+ });
1765
+
387
1766
  //#endregion
388
1767
  //#region src/utils/tsconfig-utils.ts
389
1768
  /**
@@ -421,7 +1800,7 @@ function resolveAliasPathUsingTsConfig(inputPath, projectRoot) {
421
1800
  }
422
1801
 
423
1802
  //#endregion
424
- //#region src/commands/docs.ts
1803
+ //#region src/commands/docs-build.ts
425
1804
  async function generateDocs({ cwd, outDir, verbose }) {
426
1805
  const DEBUG = process.env.BEJAMAS_DEBUG === "1" || process.env.BEJAMAS_DEBUG === "true" || verbose;
427
1806
  try {
@@ -460,7 +1839,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
460
1839
  if (mapped.startsWith("./") || mapped.startsWith("../") || isAbsolute(mapped)) outResolved = mapped;
461
1840
  else {
462
1841
  const abs = resolveAliasPathUsingTsConfig(mapped, projectRoot);
463
- if (abs) outResolved = relative$1(projectRoot, abs);
1842
+ if (abs) outResolved = relative(projectRoot, abs);
464
1843
  }
465
1844
  if (!outResolved && mapped.startsWith("@/")) outResolved = mapped.replace(/^@\//, "src/");
466
1845
  const finalOut = outResolved ?? mapped;
@@ -486,7 +1865,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
486
1865
  for (let i = 0; i < 6; i += 1) {
487
1866
  if (existsSync(resolve(current, "packages/ui/package.json"))) {
488
1867
  const abs = resolve(current, "packages/ui");
489
- return relative$1(shellCwd, abs) || abs;
1868
+ return relative(shellCwd, abs) || abs;
490
1869
  }
491
1870
  const parent = resolve(current, "..");
492
1871
  if (parent === current) break;
@@ -494,7 +1873,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
494
1873
  }
495
1874
  if (existsSync(resolve(shellCwd, "node_modules/@bejamas/ui/package.json"))) {
496
1875
  const abs = resolve(shellCwd, "node_modules/@bejamas/ui");
497
- return relative$1(shellCwd, abs) || abs;
1876
+ return relative(shellCwd, abs) || abs;
498
1877
  }
499
1878
  return "packages/ui";
500
1879
  })(),
@@ -533,7 +1912,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
533
1912
  if (process.env.BEJAMAS_DOCS_CWD) logger.info(`Docs CWD: ${process.env.BEJAMAS_DOCS_CWD}`);
534
1913
  if (process.env.BEJAMAS_DOCS_OUT_DIR) logger.info(`Docs out: ${process.env.BEJAMAS_DOCS_OUT_DIR}`);
535
1914
  }
536
- const mod = await import("./generate-mdx-BejBMCLk.js");
1915
+ const mod = await import("./generate-mdx-PPNpe9_J.js");
537
1916
  if (typeof mod.runDocsGenerator === "function") await mod.runDocsGenerator();
538
1917
  else throw new Error("Failed to load docs generator. Export 'runDocsGenerator' not found.");
539
1918
  } catch (err) {
@@ -541,7 +1920,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
541
1920
  process.exit(1);
542
1921
  }
543
1922
  }
544
- const docs = new Command().name("docs:build").alias("docs").description("generate docs from @bejamas/ui components").option("-c, --cwd <cwd>", "path to UI working directory").option("-o, --out <outDir>", "output directory for generated MDX files").action(async (opts) => {
1923
+ const docsBuild = new Command().name("docs:build").description("generate docs from @bejamas/ui components").option("-c, --cwd <cwd>", "path to UI working directory").option("-o, --out <outDir>", "output directory for generated MDX files").action(async (opts) => {
545
1924
  await generateDocs({
546
1925
  cwd: opts.cwd,
547
1926
  outDir: opts.out,
@@ -639,19 +2018,19 @@ async function checkDocs({ cwd, json }) {
639
2018
  logger.error("Unable to locate @bejamas/ui. Use --cwd to specify the UI package path.");
640
2019
  process.exit(1);
641
2020
  }
642
- const componentsDir = join$1(uiRoot, "src", "components");
2021
+ const componentsDir = join(uiRoot, "src", "components");
643
2022
  if (!existsSync(componentsDir)) {
644
2023
  logger.error(`Components directory not found: ${componentsDir}\n\nExpected structure: <uiRoot>/src/components/*.astro\nUse --cwd to specify a different UI package root.`);
645
2024
  process.exit(1);
646
2025
  }
647
- const files = readdirSync(componentsDir, { withFileTypes: true }).filter((e) => e.isFile() && extname$1(e.name).toLowerCase() === ".astro").map((e) => e.name).sort();
2026
+ const files = readdirSync(componentsDir, { withFileTypes: true }).filter((e) => e.isFile() && extname(e.name).toLowerCase() === ".astro").map((e) => e.name).sort();
648
2027
  if (files.length === 0) {
649
2028
  logger.warn("No .astro component files found.");
650
2029
  process.exit(0);
651
2030
  }
652
2031
  const results = [];
653
2032
  for (const file of files) {
654
- const status = checkComponentDocs(join$1(componentsDir, file), file);
2033
+ const status = checkComponentDocs(join(componentsDir, file), file);
655
2034
  results.push(status);
656
2035
  }
657
2036
  const complete = results.filter((r) => r.status === "complete");
@@ -718,174 +2097,8 @@ const docsCheck = new Command().name("docs:check").description("check documentat
718
2097
  });
719
2098
  });
720
2099
 
721
- //#endregion
722
- //#region src/utils/astro-imports.ts
723
- function updateImportAliases(moduleSpecifier, config, isRemote = false) {
724
- if (!moduleSpecifier.startsWith("@/") && !isRemote) return moduleSpecifier;
725
- let specifier = moduleSpecifier;
726
- if (isRemote && specifier.startsWith("@/")) specifier = specifier.replace(/^@\//, "@/registry/new-york/");
727
- if (!specifier.startsWith("@/registry/")) {
728
- const alias = config.aliases.components.split("/")[0];
729
- return specifier.replace(/^@\//, `${alias}/`);
730
- }
731
- if (specifier.match(/^@\/registry\/(.+)\/ui/)) return specifier.replace(/^@\/registry\/(.+)\/ui/, config.aliases.ui ?? `${config.aliases.components}/ui`);
732
- if (config.aliases.components && specifier.match(/^@\/registry\/(.+)\/components/)) return specifier.replace(/^@\/registry\/(.+)\/components/, config.aliases.components);
733
- if (config.aliases.lib && specifier.match(/^@\/registry\/(.+)\/lib/)) return specifier.replace(/^@\/registry\/(.+)\/lib/, config.aliases.lib);
734
- if (config.aliases.hooks && specifier.match(/^@\/registry\/(.+)\/hooks/)) return specifier.replace(/^@\/registry\/(.+)\/hooks/, config.aliases.hooks);
735
- return specifier.replace(/^@\/registry\/[^/]+/, config.aliases.components);
736
- }
737
- function rewriteAstroImports(content, config) {
738
- let updated = content;
739
- const utilsAlias = config.aliases?.utils;
740
- const utilsImport = `${typeof utilsAlias === "string" && utilsAlias.includes("/") ? utilsAlias.split("/")[0] : "@"}/lib/utils`;
741
- updated = updated.replace(/import\s+([\s\S]*?)\s+from\s+["']([^"']+)["']/g, (full, importsPart, specifier) => {
742
- const next = updateImportAliases(specifier, config, false);
743
- let finalSpec = next;
744
- if (typeof importsPart === "string" && importsPart.split(/[{},\s]/).some((part) => part === "cn") && config.aliases.utils && (next === utilsImport || next === "@/lib/utils")) finalSpec = utilsImport === next ? next.replace(utilsImport, config.aliases.utils) : config.aliases.utils;
745
- if (finalSpec === specifier) return full;
746
- return full.replace(specifier, finalSpec);
747
- });
748
- updated = updated.replace(/import\s+["']([^"']+)["']/g, (full, specifier) => {
749
- const next = updateImportAliases(specifier, config, false);
750
- if (next === specifier) return full;
751
- return full.replace(specifier, next);
752
- });
753
- return updated;
754
- }
755
- async function fixAstroImports(cwd, isVerbose) {
756
- const config = await getConfig(cwd);
757
- if (!config) return;
758
- const searchRoots = new Set([config.resolvedPaths.components, config.resolvedPaths.ui]);
759
- for (const root of Array.from(searchRoots)) {
760
- if (!root) continue;
761
- const astroFiles = await fg("**/*.astro", {
762
- cwd: root,
763
- absolute: true,
764
- dot: false
765
- });
766
- for (const filePath of astroFiles) {
767
- const original = await fs$1.readFile(filePath, "utf8");
768
- const rewritten = rewriteAstroImports(original, config);
769
- if (rewritten === original) continue;
770
- await fs$1.writeFile(filePath, rewritten, "utf8");
771
- if (isVerbose) logger.info(`[bejamas-ui] fixed imports in ${path$1.relative(cwd, filePath)}`);
772
- }
773
- }
774
- }
775
-
776
- //#endregion
777
- //#region src/utils/reorganize-components.ts
778
- /**
779
- * Fetches a registry item JSON from the registry URL.
780
- */
781
- async function fetchRegistryItem(componentName, registryUrl) {
782
- const url = `${registryUrl}/styles/new-york-v4/${componentName}.json`;
783
- try {
784
- const response = await fetch(url);
785
- if (!response.ok) {
786
- const fallbackUrl = `${registryUrl}/${componentName}.json`;
787
- const fallbackResponse = await fetch(fallbackUrl);
788
- if (!fallbackResponse.ok) return null;
789
- return await fallbackResponse.json();
790
- }
791
- return await response.json();
792
- } catch {
793
- return null;
794
- }
795
- }
796
- /**
797
- * Extracts the subfolder name from registry file paths.
798
- * E.g., "components/ui/avatar/Avatar.astro" → "avatar"
799
- */
800
- function getSubfolderFromPaths(files) {
801
- const uiFiles = files.filter((f) => f.type === "registry:ui");
802
- if (uiFiles.length < 2) return null;
803
- const subfolders = /* @__PURE__ */ new Set();
804
- for (const file of uiFiles) {
805
- const parts = file.path.split("/");
806
- const uiIndex = parts.indexOf("ui");
807
- if (uiIndex !== -1 && parts.length > uiIndex + 2) subfolders.add(parts[uiIndex + 1]);
808
- }
809
- if (subfolders.size === 1) return Array.from(subfolders)[0];
810
- if (uiFiles.length > 0) {
811
- const firstPath = uiFiles[0].path;
812
- const dirname$1 = path$1.dirname(firstPath);
813
- const folderName = path$1.basename(dirname$1);
814
- if (folderName && folderName !== "ui") return folderName;
815
- }
816
- return null;
817
- }
818
- /**
819
- * Checks if a path exists.
820
- */
821
- async function pathExists(filePath) {
822
- try {
823
- await fs$1.access(filePath);
824
- return true;
825
- } catch {
826
- return false;
827
- }
828
- }
829
- /**
830
- * Reorganizes multi-file components into correct subfolders.
831
- * Only moves files from FLAT location to subfolder.
832
- * Does NOT touch files already in subfolders.
833
- * E.g., moves `uiDir/Avatar.astro` to `uiDir/avatar/Avatar.astro`.
834
- * Returns info about moved files for display purposes.
835
- */
836
- async function reorganizeComponents(components, uiDir, registryUrl, verbose) {
837
- const result = {
838
- totalMoved: 0,
839
- movedFiles: [],
840
- skippedFiles: []
841
- };
842
- if (!uiDir || components.length === 0) return result;
843
- for (const componentName of components) try {
844
- const registryItem = await fetchRegistryItem(componentName, registryUrl);
845
- if (!registryItem) {
846
- if (verbose) logger.info(`[bejamas-ui] Could not fetch registry for ${componentName}, skipping reorganization`);
847
- continue;
848
- }
849
- const subfolder = getSubfolderFromPaths(registryItem.files);
850
- if (!subfolder) {
851
- if (verbose) logger.info(`[bejamas-ui] ${componentName} is single-file or has no subfolder, skipping`);
852
- continue;
853
- }
854
- const uiFiles = registryItem.files.filter((f) => f.type === "registry:ui");
855
- const targetDir = path$1.join(uiDir, subfolder);
856
- let movedCount = 0;
857
- for (const file of uiFiles) {
858
- const filename = path$1.basename(file.path);
859
- const flatPath = path$1.join(uiDir, filename);
860
- const targetPath = path$1.join(targetDir, filename);
861
- if (!await pathExists(flatPath)) continue;
862
- if (await pathExists(targetPath)) {
863
- try {
864
- await fs$1.unlink(flatPath);
865
- result.skippedFiles.push(`${subfolder}/${filename}`);
866
- if (verbose) logger.info(`[bejamas-ui] Removed flat duplicate: ${filename} (${subfolder}/${filename} exists)`);
867
- } catch {
868
- result.skippedFiles.push(`${subfolder}/${filename}`);
869
- }
870
- continue;
871
- }
872
- await fs$1.mkdir(targetDir, { recursive: true });
873
- await fs$1.rename(flatPath, targetPath);
874
- movedCount++;
875
- result.totalMoved++;
876
- result.movedFiles.push(`${subfolder}/${filename}`);
877
- if (verbose) logger.info(`[bejamas-ui] Moved ${filename} → ${subfolder}/${filename}`);
878
- }
879
- if (movedCount > 0 && verbose) logger.info(`[bejamas-ui] Reorganized ${componentName} into ${subfolder}/`);
880
- } catch (err) {
881
- if (verbose) logger.warn(`[bejamas-ui] Failed to reorganize ${componentName}: ${err}`);
882
- }
883
- return result;
884
- }
885
-
886
2100
  //#endregion
887
2101
  //#region src/commands/add.ts
888
- const DEFAULT_REGISTRY_URL = "https://ui.bejamas.com/r";
889
2102
  function extractOptionsForShadcn(rawArgv, cmd) {
890
2103
  if (typeof cmd.getOptionValueSource === "function") {
891
2104
  const opts = cmd.optsWithGlobals();
@@ -903,13 +2116,25 @@ function extractOptionsForShadcn(rawArgv, cmd) {
903
2116
  const value = opts[key];
904
2117
  if (typeof value === "string") forwarded$1.push(flag, value);
905
2118
  };
2119
+ const addOptionalString = (key, flag) => {
2120
+ if (getSource(key) !== "cli") return;
2121
+ const value = opts[key];
2122
+ if (value === true) {
2123
+ forwarded$1.push(flag);
2124
+ return;
2125
+ }
2126
+ if (typeof value === "string") forwarded$1.push(flag, value);
2127
+ };
906
2128
  addBoolean("yes", "--yes");
907
2129
  addBoolean("overwrite", "--overwrite");
2130
+ addBoolean("dryRun", "--dry-run");
908
2131
  addString("cwd", "--cwd");
909
2132
  addBoolean("all", "--all");
910
2133
  addString("path", "--path");
911
2134
  addBoolean("silent", "--silent");
912
2135
  addBoolean("srcDir", "--src-dir", "--no-src-dir");
2136
+ addOptionalString("diff", "--diff");
2137
+ addOptionalString("view", "--view");
913
2138
  const addIndex$1 = rawArgv.findIndex((arg) => arg === "add");
914
2139
  if (addIndex$1 !== -1) {
915
2140
  const rest$1 = rawArgv.slice(addIndex$1 + 1);
@@ -926,13 +2151,15 @@ function extractOptionsForShadcn(rawArgv, cmd) {
926
2151
  "-c",
927
2152
  "--cwd",
928
2153
  "-p",
929
- "--path"
2154
+ "--path",
2155
+ "--diff",
2156
+ "--view"
930
2157
  ]);
931
2158
  const filteredFlags = new Set(["-v", "--verbose"]);
932
- for (let i = 0; i < rest.length; i += 1) {
933
- const token = rest[i];
2159
+ for (let index = 0; index < rest.length; index += 1) {
2160
+ const token = rest[index];
934
2161
  if (token === "--") {
935
- forwarded.push("--", ...rest.slice(i + 1));
2162
+ forwarded.push("--", ...rest.slice(index + 1));
936
2163
  break;
937
2164
  }
938
2165
  if (!token.startsWith("-")) continue;
@@ -940,36 +2167,38 @@ function extractOptionsForShadcn(rawArgv, cmd) {
940
2167
  forwarded.push(token);
941
2168
  if (token.includes("=")) continue;
942
2169
  if (optionsWithValues.has(token)) {
943
- const next = rest[i + 1];
2170
+ const next = rest[index + 1];
944
2171
  if (next) {
945
2172
  forwarded.push(next);
946
- i += 1;
2173
+ index += 1;
947
2174
  }
948
2175
  }
949
2176
  }
950
2177
  return forwarded;
951
2178
  }
952
- /** Build maps for path rewriting, handling filename collisions */
953
- async function buildSubfolderMap(components, registryUrl) {
2179
+ function hasInspectionFlags(forwardedOptions) {
2180
+ return forwardedOptions.includes("--dry-run") || forwardedOptions.includes("--diff") || forwardedOptions.includes("--view");
2181
+ }
2182
+ function formatSkippedFilesHeading(count, overwriteUsed) {
2183
+ const noun = `file${count === 1 ? "" : "s"}`;
2184
+ if (overwriteUsed) return `Skipped ${count} ${noun}: (files might be identical)`;
2185
+ return `Skipped ${count} ${noun}: (files might be identical, use --overwrite to overwrite)`;
2186
+ }
2187
+ async function buildSubfolderMap(components, uiDir, registryUrl, style) {
954
2188
  const filenameToSubfolders = /* @__PURE__ */ new Map();
955
- const componentInfo = /* @__PURE__ */ new Map();
2189
+ let requiresReorganization = false;
956
2190
  for (const componentName of components) {
957
- const registryItem = await fetchRegistryItem(componentName, registryUrl);
2191
+ const registryItem = await fetchRegistryItem(componentName, registryUrl, style);
958
2192
  if (!registryItem) continue;
2193
+ if (shouldReorganizeRegistryUiFiles(registryItem.files, uiDir)) requiresReorganization = true;
959
2194
  const subfolder = getSubfolderFromPaths(registryItem.files);
960
2195
  if (!subfolder) continue;
961
- const files = [];
962
2196
  for (const file of registryItem.files) if (file.type === "registry:ui") {
963
- const filename = path$1.basename(file.path);
964
- files.push(filename);
2197
+ const filename = path.basename(file.path);
965
2198
  const subfolders = filenameToSubfolders.get(filename) || [];
966
2199
  subfolders.push(subfolder);
967
2200
  filenameToSubfolders.set(filename, subfolders);
968
2201
  }
969
- componentInfo.set(subfolder, {
970
- subfolder,
971
- files
972
- });
973
2202
  }
974
2203
  const uniqueMap = /* @__PURE__ */ new Map();
975
2204
  const sharedFilenames = /* @__PURE__ */ new Set();
@@ -979,35 +2208,29 @@ async function buildSubfolderMap(components, registryUrl) {
979
2208
  });
980
2209
  return {
981
2210
  uniqueMap,
982
- componentInfo,
983
- sharedFilenames
2211
+ sharedFilenames,
2212
+ requiresReorganization
984
2213
  };
985
2214
  }
986
- /**
987
- * Rewrite file paths to include correct subfolders.
988
- * Handles shared filenames (like index.ts) by tracking current component context.
989
- */
990
2215
  function rewritePaths(paths, mapResult) {
991
- const { uniqueMap, componentInfo, sharedFilenames } = mapResult;
2216
+ const { uniqueMap, sharedFilenames } = mapResult;
992
2217
  let currentSubfolder = null;
993
2218
  return paths.map((filePath) => {
994
- const filename = path$1.basename(filePath);
995
- const parentDir = path$1.basename(path$1.dirname(filePath));
2219
+ const filename = path.basename(filePath);
2220
+ const parentDir = path.basename(path.dirname(filePath));
996
2221
  const uniqueMapping = uniqueMap.get(filename);
997
2222
  if (uniqueMapping) {
998
- const expectedSubfolder = path$1.dirname(uniqueMapping);
2223
+ const expectedSubfolder = path.dirname(uniqueMapping);
999
2224
  currentSubfolder = expectedSubfolder;
1000
- if (parentDir !== expectedSubfolder) return `${path$1.dirname(filePath)}/${uniqueMapping}`;
2225
+ if (parentDir !== expectedSubfolder) return `${path.dirname(filePath)}/${uniqueMapping}`;
1001
2226
  return filePath;
1002
2227
  }
1003
2228
  if (sharedFilenames.has(filename) && currentSubfolder) {
1004
- const expectedSubfolder = currentSubfolder;
1005
- if (parentDir !== expectedSubfolder) return `${path$1.dirname(filePath)}/${expectedSubfolder}/${filename}`;
2229
+ if (parentDir !== currentSubfolder) return `${path.dirname(filePath)}/${currentSubfolder}/${filename}`;
1006
2230
  }
1007
2231
  return filePath;
1008
2232
  });
1009
2233
  }
1010
- /** Fetch available components from the registry */
1011
2234
  async function fetchAvailableComponents(registryUrl) {
1012
2235
  const indexUrl = `${registryUrl}/index.json`;
1013
2236
  const response = await fetch(indexUrl);
@@ -1015,14 +2238,13 @@ async function fetchAvailableComponents(registryUrl) {
1015
2238
  const data = await response.json();
1016
2239
  return Array.isArray(data) ? data : [];
1017
2240
  }
1018
- /** Prompt user to select components interactively */
1019
2241
  async function promptForComponents(registryUrl) {
1020
2242
  const checkingSpinner = spinner("Checking registry.").start();
1021
2243
  let components;
1022
2244
  try {
1023
2245
  components = await fetchAvailableComponents(registryUrl);
1024
2246
  checkingSpinner.succeed();
1025
- } catch (error) {
2247
+ } catch {
1026
2248
  checkingSpinner.fail();
1027
2249
  logger.error("Failed to fetch available components from registry.");
1028
2250
  return null;
@@ -1035,9 +2257,9 @@ async function promptForComponents(registryUrl) {
1035
2257
  type: "autocompleteMultiselect",
1036
2258
  name: "selected",
1037
2259
  message: "Which components would you like to add?",
1038
- choices: components.filter((c) => !c.type || c.type === "registry:ui").map((c) => ({
1039
- title: c.name,
1040
- value: c.name
2260
+ choices: components.filter((component) => !component.type || component.type === "registry:ui").map((component) => ({
2261
+ title: component.name,
2262
+ value: component.name
1041
2263
  })),
1042
2264
  hint: "- Space to select. Return to submit.",
1043
2265
  instructions: false
@@ -1045,7 +2267,6 @@ async function promptForComponents(registryUrl) {
1045
2267
  if (!selected) return null;
1046
2268
  return selected;
1047
2269
  }
1048
- /** Parse shadcn output to extract file lists (stdout has paths, stderr has headers) */
1049
2270
  function parseShadcnOutput(stdout, stderr) {
1050
2271
  const result = {
1051
2272
  created: [],
@@ -1072,23 +2293,34 @@ function parseShadcnOutput(stdout, stderr) {
1072
2293
  if (!allPaths.includes(filePath)) allPaths.push(filePath);
1073
2294
  }
1074
2295
  }
1075
- let idx = 0;
1076
- for (let i = 0; i < createdCount && idx < allPaths.length; i++) result.created.push(allPaths[idx++]);
1077
- for (let i = 0; i < updatedCount && idx < allPaths.length; i++) result.updated.push(allPaths[idx++]);
1078
- for (let i = 0; i < skippedCount && idx < allPaths.length; i++) result.skipped.push(allPaths[idx++]);
2296
+ let index = 0;
2297
+ for (let count = 0; count < createdCount && index < allPaths.length; count += 1) {
2298
+ result.created.push(allPaths[index]);
2299
+ index += 1;
2300
+ }
2301
+ for (let count = 0; count < updatedCount && index < allPaths.length; count += 1) {
2302
+ result.updated.push(allPaths[index]);
2303
+ index += 1;
2304
+ }
2305
+ for (let count = 0; count < skippedCount && index < allPaths.length; count += 1) {
2306
+ result.skipped.push(allPaths[index]);
2307
+ index += 1;
2308
+ }
1079
2309
  return result;
1080
2310
  }
1081
- async function addComponents(packages, forwardedOptions, isVerbose, isSilent, subfolderMapResult) {
2311
+ async function addComponents(cwd, packages, forwardedOptions, isVerbose, isSilent, inspectionMode) {
1082
2312
  const env = {
1083
2313
  ...process.env,
1084
- REGISTRY_URL: process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL
2314
+ REGISTRY_URL: resolveRegistryUrl()
1085
2315
  };
2316
+ await ensurePinnedShadcnExecPrefix();
1086
2317
  const invocation = buildPinnedShadcnInvocation(buildShadcnAddArgs(packages, forwardedOptions));
1087
2318
  if (isVerbose) logger.info(`[bejamas-ui] ${invocation.cmd} ${invocation.args.join(" ")}`);
1088
2319
  const registrySpinner = spinner("Checking registry.", { silent: isSilent });
1089
2320
  registrySpinner.start();
1090
2321
  try {
1091
2322
  const result = await execa(invocation.cmd, invocation.args, {
2323
+ cwd,
1092
2324
  env,
1093
2325
  input: "n\nn\nn\nn\nn\nn\nn\nn\nn\nn\n",
1094
2326
  stdout: "pipe",
@@ -1103,13 +2335,21 @@ async function addComponents(packages, forwardedOptions, isVerbose, isSilent, su
1103
2335
  logger.info(`[bejamas-ui] Raw stdout: ${stdout}`);
1104
2336
  logger.info(`[bejamas-ui] Raw stderr: ${stderr}`);
1105
2337
  }
1106
- const parsed = parseShadcnOutput(stdout, stderr);
2338
+ if (inspectionMode) {
2339
+ if (stdout) process.stdout.write(stdout.endsWith("\n") ? stdout : `${stdout}\n`);
2340
+ if (stderr) process.stderr.write(stderr.endsWith("\n") ? stderr : `${stderr}\n`);
2341
+ }
2342
+ const parsed = inspectionMode ? {
2343
+ created: [],
2344
+ updated: [],
2345
+ skipped: []
2346
+ } : parseShadcnOutput(stdout, stderr);
1107
2347
  if (result.exitCode !== 0) {
1108
2348
  if (result.stderr) logger.error(result.stderr);
1109
2349
  process.exit(result.exitCode);
1110
2350
  }
1111
2351
  return parsed;
1112
- } catch (err) {
2352
+ } catch {
1113
2353
  registrySpinner.fail();
1114
2354
  logger.error("Failed to add components");
1115
2355
  process.exit(1);
@@ -1125,22 +2365,24 @@ function buildShadcnAddArgs(packages, forwardedOptions) {
1125
2365
  ...forwardedOptions
1126
2366
  ];
1127
2367
  }
1128
- const add = new Command().name("add").description("Add components via the pinned shadcn registry flow").argument("[components...]", "Component package names to add").option("-y, --yes", "skip confirmation prompt.", false).option("-o, --overwrite", "overwrite existing files.", false).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-a, --all", "add all available components", false).option("-p, --path <path>", "the path to add the component to.").option("-s, --silent", "mute output.", false).option("--src-dir", "use the src directory when creating a new project.", false).option("--no-src-dir", "do not use the src directory when creating a new project.").action(async function action(packages, _opts, cmd) {
2368
+ const add = new Command().name("add").description("Add components via the Bejamas-managed shadcn registry flow").argument("[components...]", "Component package names to add").option("-y, --yes", "skip confirmation prompt.", false).option("-o, --overwrite", "overwrite existing files.", false).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-a, --all", "add all available components", false).option("-p, --path <path>", "the path to add the component to.").option("-s, --silent", "mute output.", false).option("--dry-run", "preview changes without writing files.", false).option("--diff [path]", "show diff for a file.").option("--view [path]", "show file contents.").option("--src-dir", "use the src directory when creating a new project.", false).option("--no-src-dir", "do not use the src directory when creating a new project.").action(async function action(packages, _opts, cmd) {
1129
2369
  const root = cmd?.parent;
1130
2370
  const verbose = Boolean(root?.opts?.().verbose);
1131
2371
  const forwardedOptions = extractOptionsForShadcn(process.argv.slice(2), cmd);
1132
2372
  const opts = typeof cmd.optsWithGlobals === "function" ? cmd.optsWithGlobals() : cmd.opts?.() ?? {};
2373
+ const inspectionMode = hasInspectionFlags(forwardedOptions);
2374
+ const overwriteUsed = forwardedOptions.includes("--overwrite") || forwardedOptions.includes("-o");
1133
2375
  const cwd = opts.cwd || process.cwd();
1134
2376
  let componentsToAdd = packages || [];
1135
2377
  const wantsAll = Boolean(opts.all);
1136
2378
  const isSilent = opts.silent || false;
1137
- const registryUrl = process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL;
2379
+ const registryUrl = resolveRegistryUrl();
1138
2380
  if (wantsAll && componentsToAdd.length === 0) {
1139
2381
  const fetchingSpinner = spinner("Fetching available components.", { silent: isSilent }).start();
1140
2382
  try {
1141
- componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter((c) => !c.type || c.type === "registry:ui").map((c) => c.name);
2383
+ componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter((component) => !component.type || component.type === "registry:ui").map((component) => component.name);
1142
2384
  fetchingSpinner.succeed();
1143
- } catch (error) {
2385
+ } catch {
1144
2386
  fetchingSpinner.fail();
1145
2387
  logger.error("Failed to fetch available components from registry.");
1146
2388
  process.exit(1);
@@ -1166,19 +2408,35 @@ const add = new Command().name("add").description("Add components via the pinned
1166
2408
  logger.info(`[bejamas-ui] uiDir: ${uiDir}`);
1167
2409
  logger.info(`[bejamas-ui] aliases.ui: ${uiConfig?.aliases?.ui || "not set"}`);
1168
2410
  }
2411
+ const activeStyle = uiConfig?.style || config?.style || "bejamas-juno";
1169
2412
  const totalComponents = componentsToAdd.length;
1170
- for (let i = 0; i < componentsToAdd.length; i++) {
1171
- const component = componentsToAdd[i];
2413
+ for (let index = 0; index < componentsToAdd.length; index += 1) {
2414
+ const component = componentsToAdd[index];
1172
2415
  if (totalComponents > 1 && !isSilent) {
1173
2416
  logger.break();
1174
- logger.info(highlighter.info(`[${i + 1}/${totalComponents}]`) + ` Adding ${highlighter.success(component)}...`);
2417
+ logger.info(highlighter.info(`[${index + 1}/${totalComponents}]`) + ` Adding ${highlighter.success(component)}...`);
2418
+ }
2419
+ const subfolderMapResult = inspectionMode ? {
2420
+ uniqueMap: /* @__PURE__ */ new Map(),
2421
+ sharedFilenames: /* @__PURE__ */ new Set(),
2422
+ requiresReorganization: false
2423
+ } : await buildSubfolderMap([component], uiDir, registryUrl, activeStyle);
2424
+ const parsed = await addComponents(cwd, [component], forwardedOptions, verbose, isSilent, inspectionMode);
2425
+ if (!inspectionMode) {
2426
+ await syncManagedTailwindCss(cwd);
2427
+ const registryItem = await fetchRegistryItem(component, registryUrl, activeStyle);
2428
+ if (registryItem?.type === "registry:font") {
2429
+ const nextFont = toManagedAstroFont(registryItem.name);
2430
+ if (nextFont) {
2431
+ await syncAstroFontsInProject(cwd, mergeManagedAstroFonts(await readManagedAstroFontsFromProject(cwd), nextFont), nextFont.cssVariable);
2432
+ await syncAstroManagedFontCss(cwd, nextFont.cssVariable);
2433
+ await cleanupAstroFontPackages(cwd);
2434
+ }
2435
+ }
1175
2436
  }
1176
- const subfolderMapResult = await buildSubfolderMap([component], registryUrl);
1177
- const parsed = await addComponents([component], forwardedOptions, verbose, isSilent, subfolderMapResult);
1178
2437
  let skippedCount = 0;
1179
- if (uiDir) skippedCount = (await reorganizeComponents([component], uiDir, registryUrl, verbose)).skippedFiles.length;
1180
- if (!isSilent) {
1181
- uiDir && path$1.relative(cwd, uiDir);
2438
+ if (!inspectionMode && uiDir && subfolderMapResult.requiresReorganization) skippedCount = (await reorganizeComponents([component], uiDir, registryUrl, verbose, activeStyle)).skippedFiles.length;
2439
+ if (!isSilent && !inspectionMode) {
1182
2440
  const actuallyCreated = Math.max(0, parsed.created.length - skippedCount);
1183
2441
  if (actuallyCreated > 0) {
1184
2442
  const createdPaths = rewritePaths(parsed.created.slice(0, actuallyCreated), subfolderMapResult);
@@ -1193,13 +2451,36 @@ const add = new Command().name("add").description("Add components via the pinned
1193
2451
  if (skippedCount > 0) logger.info(`Skipped ${skippedCount} file${skippedCount > 1 ? "s" : ""}: (already exists)`);
1194
2452
  if (parsed.skipped.length > 0) {
1195
2453
  const skippedPaths = rewritePaths(parsed.skipped, subfolderMapResult);
1196
- logger.info(`Skipped ${skippedPaths.length} file${skippedPaths.length > 1 ? "s" : ""}: (use --overwrite)`);
2454
+ logger.info(formatSkippedFilesHeading(skippedPaths.length, overwriteUsed));
1197
2455
  for (const file of skippedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
1198
2456
  }
1199
2457
  if (actuallyCreated === 0 && parsed.updated.length === 0 && skippedCount === 0 && parsed.skipped.length === 0) logger.info("Already up to date.");
1200
2458
  }
1201
2459
  }
1202
- await fixAstroImports(cwd, verbose);
2460
+ if (!inspectionMode) await fixAstroImports(cwd, verbose);
2461
+ });
2462
+
2463
+ //#endregion
2464
+ //#region src/commands/info.ts
2465
+ const info = new Command().name("info").description("proxy to shadcn info").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("--json", "output as JSON.", false).action(async (opts) => {
2466
+ const cwd = path.resolve(opts.cwd ?? process.cwd());
2467
+ const rawArgv = process.argv.slice(2);
2468
+ const args = [
2469
+ "info",
2470
+ "--cwd",
2471
+ cwd
2472
+ ];
2473
+ if (opts.json) args.push("--json");
2474
+ const passthroughArgs = extractPassthroughArgs(rawArgv, "info");
2475
+ if (passthroughArgs.length > 0) args.push(...passthroughArgs);
2476
+ try {
2477
+ await runShadcnCommand({
2478
+ cwd,
2479
+ args
2480
+ });
2481
+ } catch {
2482
+ process.exit(1);
2483
+ }
1203
2484
  });
1204
2485
 
1205
2486
  //#endregion
@@ -1208,7 +2489,9 @@ const pkg = createRequire(import.meta.url)("../package.json");
1208
2489
  const program = new Command().name("bejamas").description("bejamas/ui cli").configureHelp({ helpWidth: Math.min(100, process.stdout.columns || 100) }).version(pkg.version, "-v, --version", "output the version number");
1209
2490
  program.addCommand(init);
1210
2491
  program.addCommand(add);
2492
+ program.addCommand(info);
1211
2493
  program.addCommand(docs);
2494
+ program.addCommand(docsBuild);
1212
2495
  program.addCommand(docsCheck);
1213
2496
  program.parse(process.argv);
1214
2497
  var src_default = program;