bejamas 0.2.12 → 0.3.1

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,31 @@
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 * as tar from "tar";
20
+ import os$1 from "node:os";
18
21
 
22
+ //#region src/registry/context.ts
23
+ let context = { headers: {} };
24
+ function clearRegistryContext() {
25
+ context.headers = {};
26
+ }
27
+
28
+ //#endregion
19
29
  //#region src/utils/errors.ts
20
30
  const MISSING_DIR_OR_EMPTY_PROJECT = "1";
21
31
 
@@ -23,7 +33,7 @@ const MISSING_DIR_OR_EMPTY_PROJECT = "1";
23
33
  //#region src/preflights/preflight-init.ts
24
34
  async function preFlightInit(options) {
25
35
  const errors = {};
26
- if (!fs.existsSync(options.cwd) || !fs.existsSync(path.resolve(options.cwd, "package.json"))) {
36
+ if (!fs.existsSync(options.cwd) || !fs.existsSync(path$1.resolve(options.cwd, "package.json"))) {
27
37
  errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
28
38
  return {
29
39
  errors,
@@ -37,10 +47,986 @@ async function preFlightInit(options) {
37
47
  }
38
48
 
39
49
  //#endregion
40
- //#region src/registry/context.ts
41
- let context = { headers: {} };
42
- function clearRegistryContext() {
43
- context.headers = {};
50
+ //#region src/utils/astro-fonts.ts
51
+ const ASTRO_CONFIG_BLOCK_START = "// bejamas:astro-fonts:start";
52
+ const ASTRO_CONFIG_BLOCK_END = "// bejamas:astro-fonts:end";
53
+ const ASTRO_LAYOUT_BLOCK_START = "<!-- bejamas:astro-fonts:start -->";
54
+ const ASTRO_LAYOUT_BLOCK_END = "<!-- bejamas:astro-fonts:end -->";
55
+ const ASTRO_FONT_CONSTANT = "BEJAMAS_ASTRO_FONTS";
56
+ const ASTRO_CONFIG_CANDIDATES = [
57
+ "astro.config.mjs",
58
+ "apps/web/astro.config.mjs",
59
+ "apps/docs/astro.config.mjs"
60
+ ];
61
+ const ASTRO_LAYOUT_CANDIDATES = ["src/layouts/Layout.astro", "apps/web/src/layouts/Layout.astro"];
62
+ const STARLIGHT_HEAD_RELATIVE_PATH = "apps/docs/src/components/Head.astro";
63
+ const ASTRO_FONT_PROVIDERS = ["fontsource", "google"];
64
+ const ASTRO_FONTSOURCE_FONT_NAMES = new Set(["geist", "geist-mono"]);
65
+ const MANAGED_BODY_FONT_CLASSES = new Set(["font-mono", "font-serif"]);
66
+ function compactSource$1(source) {
67
+ return source.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
68
+ }
69
+ function escapeRegExp$1(value) {
70
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
71
+ }
72
+ function upsertImport(source, moduleName, importedName, fallbackNames = []) {
73
+ const importPattern = /* @__PURE__ */ new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']${escapeRegExp$1(moduleName)}["'];?`);
74
+ if (importPattern.test(source)) return source.replace(importPattern, (_match, importsSource) => {
75
+ const imports = importsSource.split(",").map((value) => value.trim()).filter(Boolean);
76
+ if (!imports.includes(importedName)) imports.push(importedName);
77
+ for (const name of fallbackNames) if (!imports.includes(name)) imports.unshift(name);
78
+ return `import { ${Array.from(new Set(imports)).join(", ")} } from "${moduleName}";`;
79
+ });
80
+ const importLine = `import { ${Array.from(new Set([...fallbackNames, importedName])).join(", ")} } from "${moduleName}";`;
81
+ const frontmatterEnd = source.indexOf("---", 3);
82
+ if (source.startsWith("---") && frontmatterEnd !== -1) return `${source.slice(0, frontmatterEnd).trimEnd()}\n${importLine}\n${source.slice(frontmatterEnd)}`;
83
+ return `${importLine}\n${source}`;
84
+ }
85
+ function buildAstroConfigFontBlock(fontsToSerialize) {
86
+ 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}`;
87
+ }
88
+ function buildAstroLayoutFontBlock(fontsToSerialize) {
89
+ return `${ASTRO_LAYOUT_BLOCK_START}\n${fontsToSerialize.map((font) => `<Font cssVariable="${font.cssVariable}" />`).join("\n")}\n${ASTRO_LAYOUT_BLOCK_END}`;
90
+ }
91
+ function bodyFontClassFromCssVariable(cssVariable) {
92
+ if (cssVariable === "--font-mono" || cssVariable === "--font-serif") return fontClassFromCssVariable(cssVariable);
93
+ return "";
94
+ }
95
+ function upsertManagedBodyFontClass(source, cssVariable) {
96
+ const bodyClass = bodyFontClassFromCssVariable(cssVariable);
97
+ const bodyTagPattern = /<body\b([^>]*?)(\/?)>/m;
98
+ if (!bodyTagPattern.test(source)) return source;
99
+ return source.replace(bodyTagPattern, (_match, attrs, selfClosing) => {
100
+ const classPattern = /\sclass=(["'])(.*?)\1/m;
101
+ const classMatch = attrs.match(classPattern);
102
+ const quote = classMatch?.[1] ?? "\"";
103
+ const existingClasses = (classMatch?.[2] ?? "").split(/\s+/).filter(Boolean).filter((className) => !MANAGED_BODY_FONT_CLASSES.has(className));
104
+ const nextClasses = bodyClass ? Array.from(new Set([...existingClasses, bodyClass])) : existingClasses;
105
+ let nextAttrs = attrs.replace(classPattern, "");
106
+ if (nextClasses.length > 0) nextAttrs = `${nextAttrs} class=${quote}${nextClasses.join(" ")}${quote}`;
107
+ return `<body${nextAttrs}${selfClosing}>`;
108
+ });
109
+ }
110
+ function parseManagedAstroFonts(source) {
111
+ 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)}`);
112
+ const match = source.match(markerPattern);
113
+ if (!match) return [];
114
+ return (match[1].match(/\{[\s\S]*?\}/g) ?? []).map((entry) => {
115
+ const name = entry.match(/name:\s*"([^"]+)"/)?.[1];
116
+ const cssVariable = entry.match(/cssVariable:\s*"([^"]+)"/)?.[1];
117
+ const provider = entry.match(/provider:\s*fontProviders\.([a-z]+)\(\)/)?.[1];
118
+ const subsetsSource = entry.match(/subsets:\s*(\[[^\]]*\])/s)?.[1];
119
+ if (!name || !cssVariable || !provider || !ASTRO_FONT_PROVIDERS.includes(provider)) return null;
120
+ let subsets = ["latin"];
121
+ if (subsetsSource) try {
122
+ const parsed = JSON.parse(subsetsSource);
123
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((value) => typeof value === "string")) subsets = parsed;
124
+ } catch {
125
+ subsets = ["latin"];
126
+ }
127
+ return {
128
+ name,
129
+ cssVariable,
130
+ provider,
131
+ subsets
132
+ };
133
+ }).filter((value) => value !== null);
134
+ }
135
+ function normalizeManagedAstroFontSubsets(subsets) {
136
+ if (!subsets?.length) return ["latin"];
137
+ return [subsets[0], ...subsets.slice(1)];
138
+ }
139
+ function replaceManagedBlock(source, nextBlock, start, end) {
140
+ const blockPattern = new RegExp(`${escapeRegExp$1(start)}[\\s\\S]*?${escapeRegExp$1(end)}`, "m");
141
+ if (blockPattern.test(source)) return source.replace(blockPattern, nextBlock);
142
+ return null;
143
+ }
144
+ function stripExperimentalFontsProperty(source) {
145
+ const experimentalMatch = source.match(/experimental\s*:\s*\{/);
146
+ if (!experimentalMatch || experimentalMatch.index === void 0) return source;
147
+ const propertyStart = experimentalMatch.index;
148
+ const openingBraceIndex = propertyStart + experimentalMatch[0].lastIndexOf("{");
149
+ let depth = 0;
150
+ let closingBraceIndex = -1;
151
+ let quote = null;
152
+ for (let index = openingBraceIndex; index < source.length; index += 1) {
153
+ const character = source[index];
154
+ const previousCharacter = index > 0 ? source[index - 1] : "";
155
+ if (quote) {
156
+ if (character === quote && previousCharacter !== "\\") quote = null;
157
+ continue;
158
+ }
159
+ if (character === "\"" || character === "'" || character === "`") {
160
+ quote = character;
161
+ continue;
162
+ }
163
+ if (character === "{") {
164
+ depth += 1;
165
+ continue;
166
+ }
167
+ if (character === "}") {
168
+ depth -= 1;
169
+ if (depth === 0) {
170
+ closingBraceIndex = index;
171
+ break;
172
+ }
173
+ }
174
+ }
175
+ if (closingBraceIndex === -1) return source;
176
+ 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");
177
+ let propertyEnd = closingBraceIndex + 1;
178
+ while (propertyEnd < source.length && /\s/.test(source[propertyEnd])) propertyEnd += 1;
179
+ if (source[propertyEnd] === ",") propertyEnd += 1;
180
+ if (nextBlockContent.trim().length === 0) return `${source.slice(0, propertyStart)}${source.slice(propertyEnd)}`;
181
+ const rebuiltProperty = `experimental: {${nextBlockContent}\n }`;
182
+ return `${source.slice(0, propertyStart)}${rebuiltProperty}${source.slice(closingBraceIndex + 1)}`;
183
+ }
184
+ function patchAstroConfigSource(source, fontsToSerialize, options = {}) {
185
+ let next = upsertImport(source, "astro/config", "fontProviders", ["defineConfig"]);
186
+ const fontBlock = buildAstroConfigFontBlock(fontsToSerialize);
187
+ const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_CONFIG_BLOCK_START, ASTRO_CONFIG_BLOCK_END);
188
+ if (replacedBlock) next = replacedBlock;
189
+ else {
190
+ const lastImport = Array.from(next.matchAll(/^import .*$/gm)).at(-1);
191
+ if (lastImport?.index !== void 0) {
192
+ const insertAt = lastImport.index + lastImport[0].length;
193
+ next = `${next.slice(0, insertAt)}\n\n${fontBlock}${next.slice(insertAt)}`;
194
+ } else next = `${fontBlock}\n\n${next}`;
195
+ }
196
+ next = stripExperimentalFontsProperty(next);
197
+ if (!/^\s*fonts:\s*BEJAMAS_ASTRO_FONTS\b/m.test(next)) next = next.replace(/defineConfig\(\s*\{/, `defineConfig({\n fonts: ${ASTRO_FONT_CONSTANT},`);
198
+ if (options.starlightHeadOverride) {
199
+ if (next.includes("Head: \"./src/components/Head.astro\"")) return compactSource$1(next);
200
+ const starlightComponentsPattern = /starlight\(\{\s*([\s\S]*?)components:\s*\{([\s\S]*?)\}/m;
201
+ if (starlightComponentsPattern.test(next)) next = next.replace(starlightComponentsPattern, (_match, before, inner) => `starlight({${before}components: {\n Head: "./src/components/Head.astro",${inner ? `${inner}` : ""}\n }`);
202
+ else next = next.replace(/starlight\(\s*\{/, `starlight({\n components: {\n Head: "./src/components/Head.astro",\n },`);
203
+ }
204
+ return compactSource$1(next);
205
+ }
206
+ function patchAstroLayoutSource(source, fontsToSerialize, activeFontCssVariable) {
207
+ let next = upsertImport(source, "astro:assets", "Font");
208
+ const fontBlock = buildAstroLayoutFontBlock(fontsToSerialize);
209
+ const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_LAYOUT_BLOCK_START, ASTRO_LAYOUT_BLOCK_END);
210
+ if (replacedBlock) next = replacedBlock;
211
+ else if (next.includes("<head>")) next = next.replace("<head>", `<head>\n ${fontBlock.replace(/\n/g, "\n ")}`);
212
+ else if (next.includes("</head>")) next = next.replace("</head>", ` ${fontBlock.replace(/\n/g, "\n ")}\n</head>`);
213
+ else next = `${next}\n${fontBlock}\n`;
214
+ next = upsertManagedBodyFontClass(next, activeFontCssVariable);
215
+ return compactSource$1(next);
216
+ }
217
+ function patchStarlightHeadSource(source, fontsToSerialize) {
218
+ let next = upsertImport(source, "@astrojs/starlight/components/Head.astro", "DefaultHead");
219
+ next = upsertImport(next, "astro:assets", "Font");
220
+ const fontBlock = buildAstroLayoutFontBlock(fontsToSerialize);
221
+ const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_LAYOUT_BLOCK_START, ASTRO_LAYOUT_BLOCK_END);
222
+ if (replacedBlock) next = replacedBlock;
223
+ else if (next.includes("<DefaultHead")) next = next.replace(/<DefaultHead[^>]*\/>/, (match) => `${match}\n${fontBlock}`);
224
+ else next = `${next.trimEnd()}\n\n<DefaultHead />\n${fontBlock}\n`;
225
+ return compactSource$1(next);
226
+ }
227
+ function toManagedAstroFont(fontName) {
228
+ const normalized = fontName.replace(/^font-heading-/, "").replace(/^font-/, "");
229
+ const font = fontName.startsWith("font-heading-") ? getHeadingFontValue(normalized) : getFontValue(normalized);
230
+ if (!font || font.type !== "registry:font" || !ASTRO_FONT_PROVIDERS.includes(font.font.provider)) return null;
231
+ return {
232
+ name: font.title?.replace(/\s+\(Heading\)$/, "") ?? font.font.import.replace(/_/g, " "),
233
+ cssVariable: font.font.variable,
234
+ provider: ASTRO_FONTSOURCE_FONT_NAMES.has(normalized) ? "fontsource" : "google",
235
+ subsets: normalizeManagedAstroFontSubsets(font.font.subsets)
236
+ };
237
+ }
238
+ function mergeManagedAstroFonts(currentFonts, nextFont) {
239
+ return [...currentFonts.filter((font) => font.cssVariable !== nextFont.cssVariable), nextFont].sort((left, right) => left.cssVariable.localeCompare(right.cssVariable));
240
+ }
241
+ function fontClassFromCssVariable(cssVariable) {
242
+ return `font-${cssVariable.replace(/^--font-/, "")}`;
243
+ }
244
+ async function patchFileIfExists(filePath, transformer) {
245
+ if (!await fs.pathExists(filePath)) return;
246
+ const current = await fs.readFile(filePath, "utf8");
247
+ const next = transformer(current);
248
+ if (next !== current) await fs.writeFile(filePath, next, "utf8");
249
+ }
250
+ async function collectPackageJsonsFromComponents(projectPath) {
251
+ const componentJsonFiles = await fg("**/components.json", {
252
+ cwd: projectPath,
253
+ ignore: [
254
+ "**/node_modules/**",
255
+ "**/dist/**",
256
+ "**/.astro/**"
257
+ ]
258
+ });
259
+ const packageJsonFiles = /* @__PURE__ */ new Set();
260
+ for (const relativePath of componentJsonFiles) {
261
+ const absolutePath = path.resolve(projectPath, relativePath);
262
+ const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
263
+ if (typeof cssPath !== "string" || cssPath.length === 0) continue;
264
+ let currentDir = path.dirname(path.resolve(path.dirname(absolutePath), cssPath));
265
+ while (true) {
266
+ const packageJsonPath = path.resolve(currentDir, "package.json");
267
+ if (await fs.pathExists(packageJsonPath)) {
268
+ packageJsonFiles.add(packageJsonPath);
269
+ break;
270
+ }
271
+ const parentDir = path.dirname(currentDir);
272
+ if (parentDir === currentDir) break;
273
+ currentDir = parentDir;
274
+ }
275
+ }
276
+ return packageJsonFiles;
277
+ }
278
+ async function upsertStarlightHeadFile(projectPath, fontsToSerialize) {
279
+ const headPath = path.resolve(projectPath, STARLIGHT_HEAD_RELATIVE_PATH);
280
+ 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);
281
+ await fs.ensureDir(path.dirname(headPath));
282
+ await fs.writeFile(headPath, next, "utf8");
283
+ }
284
+ async function readManagedAstroFontsFromProject(projectPath) {
285
+ for (const relativePath of ASTRO_CONFIG_CANDIDATES) {
286
+ const absolutePath = path.resolve(projectPath, relativePath);
287
+ if (!await fs.pathExists(absolutePath)) continue;
288
+ const parsed = parseManagedAstroFonts(await fs.readFile(absolutePath, "utf8"));
289
+ if (parsed.length > 0) return parsed;
290
+ }
291
+ return [];
292
+ }
293
+ async function cleanupAstroFontPackages(projectPath) {
294
+ const managedPackages = new Set(fonts.filter((font) => font.type === "registry:font").map((font) => getFontPackageName(font.name.replace(/^font-heading-/, "").replace(/^font-/, ""))));
295
+ const packageJsonFiles = await collectPackageJsonsFromComponents(projectPath);
296
+ await Promise.all(Array.from(packageJsonFiles).map(async (absolutePath) => {
297
+ const current = await fs.readJson(absolutePath);
298
+ let changed = false;
299
+ for (const bucket of ["dependencies", "devDependencies"]) {
300
+ const currentBucket = current[bucket];
301
+ if (!currentBucket || typeof currentBucket !== "object") continue;
302
+ for (const packageName of managedPackages) if (packageName in currentBucket) {
303
+ delete currentBucket[packageName];
304
+ changed = true;
305
+ }
306
+ if (Object.keys(currentBucket).length === 0) {
307
+ delete current[bucket];
308
+ changed = true;
309
+ }
310
+ }
311
+ if (changed) await fs.writeJson(absolutePath, current, { spaces: 2 });
312
+ }));
313
+ }
314
+ async function syncAstroFontsInProject(projectPath, fontsToSerialize, activeFontCssVariable) {
315
+ await Promise.all(ASTRO_CONFIG_CANDIDATES.map((relativePath) => patchFileIfExists(path.resolve(projectPath, relativePath), (source) => patchAstroConfigSource(source, fontsToSerialize, { starlightHeadOverride: relativePath === "apps/docs/astro.config.mjs" }))));
316
+ await Promise.all(ASTRO_LAYOUT_CANDIDATES.map((relativePath) => patchFileIfExists(path.resolve(projectPath, relativePath), (source) => patchAstroLayoutSource(source, fontsToSerialize, activeFontCssVariable))));
317
+ const docsConfigPath = path.resolve(projectPath, "apps/docs/astro.config.mjs");
318
+ if (await fs.pathExists(docsConfigPath)) await upsertStarlightHeadFile(projectPath, fontsToSerialize);
319
+ }
320
+
321
+ //#endregion
322
+ //#region src/utils/apply-design-system.ts
323
+ const CREATE_BLOCK_START = "/* bejamas:create:start */";
324
+ const CREATE_BLOCK_END = "/* bejamas:create:end */";
325
+ const SHADCN_TAILWIND_IMPORT = "@import \"shadcn/tailwind.css\";";
326
+ const BEJAMAS_TAILWIND_IMPORT = "@import \"bejamas/tailwind.css\";";
327
+ const MANAGED_TAILWIND_IMPORTS = new Set([SHADCN_TAILWIND_IMPORT, BEJAMAS_TAILWIND_IMPORT]);
328
+ const TEMPLATE_APP_UI_IMPORT = "import { appUi } from \"@/i18n/ui\";";
329
+ const TEMPLATE_I18N_SOURCES = {
330
+ astro: `const RTL_LANGUAGES = ["ar", "fa", "he"] as const;
331
+
332
+ export type TemplateLanguage = "en" | (typeof RTL_LANGUAGES)[number];
333
+
334
+ export const CURRENT_LANGUAGE: TemplateLanguage = "en";
335
+
336
+ const ui = {
337
+ en: {
338
+ metadataTitle: "bejamas/ui Astro project",
339
+ welcomeMessage: "Welcome to {project} Astro project.",
340
+ getStartedMessage: "Get started by editing src/pages/index.astro.",
341
+ readDocs: "Read docs",
342
+ getCustomDemo: "Get custom demo",
343
+ },
344
+ ar: {
345
+ metadataTitle: "مشروع Astro من bejamas/ui",
346
+ welcomeMessage: "مرحبًا بك في مشروع Astro الخاص بـ {project}.",
347
+ getStartedMessage: "ابدأ بتحرير src/pages/index.astro.",
348
+ readDocs: "اقرأ التوثيق",
349
+ getCustomDemo: "احصل على عرض توضيحي مخصص",
350
+ },
351
+ fa: {
352
+ metadataTitle: "پروژه Astro با bejamas/ui",
353
+ welcomeMessage: "به پروژه Astro با {project} خوش آمدید.",
354
+ getStartedMessage: "برای شروع src/pages/index.astro را ویرایش کنید.",
355
+ readDocs: "مطالعه مستندات",
356
+ getCustomDemo: "درخواست دموی سفارشی",
357
+ },
358
+ he: {
359
+ metadataTitle: "פרויקט Astro עם bejamas/ui",
360
+ welcomeMessage: "ברוכים הבאים לפרויקט Astro של {project}.",
361
+ getStartedMessage: "התחילו בעריכת src/pages/index.astro.",
362
+ readDocs: "קריאת התיעוד",
363
+ getCustomDemo: "קבלת דמו מותאם",
364
+ },
365
+ } as const satisfies Record<TemplateLanguage, Record<string, string>>;
366
+
367
+ export const appUi = {
368
+ lang: CURRENT_LANGUAGE,
369
+ dir: RTL_LANGUAGES.includes(
370
+ CURRENT_LANGUAGE as (typeof RTL_LANGUAGES)[number],
371
+ )
372
+ ? "rtl"
373
+ : "ltr",
374
+ ...ui[CURRENT_LANGUAGE],
375
+ };
376
+ `,
377
+ monorepo: `const RTL_LANGUAGES = ["ar", "fa", "he"] as const;
378
+
379
+ export type TemplateLanguage = "en" | (typeof RTL_LANGUAGES)[number];
380
+
381
+ export const CURRENT_LANGUAGE: TemplateLanguage = "en";
382
+
383
+ const ui = {
384
+ en: {
385
+ metadataTitle: "bejamas/ui Astro project",
386
+ welcomeMessage: "Welcome to {project} Astro monorepo.",
387
+ getStartedMessage: "Get started by editing apps/web/src/pages/index.astro.",
388
+ readDocs: "Read docs",
389
+ getCustomDemo: "Get custom demo",
390
+ },
391
+ ar: {
392
+ metadataTitle: "مشروع Astro من bejamas/ui",
393
+ welcomeMessage: "مرحبًا بك في مشروع Astro الأحادي من {project}.",
394
+ getStartedMessage: "ابدأ بتحرير apps/web/src/pages/index.astro.",
395
+ readDocs: "اقرأ التوثيق",
396
+ getCustomDemo: "احصل على عرض توضيحي مخصص",
397
+ },
398
+ fa: {
399
+ metadataTitle: "پروژه Astro با bejamas/ui",
400
+ welcomeMessage: "به مونوریپوی Astro با {project} خوش آمدید.",
401
+ getStartedMessage:
402
+ "برای شروع apps/web/src/pages/index.astro را ویرایش کنید.",
403
+ readDocs: "مطالعه مستندات",
404
+ getCustomDemo: "درخواست دموی سفارشی",
405
+ },
406
+ he: {
407
+ metadataTitle: "פרויקט Astro עם bejamas/ui",
408
+ welcomeMessage: "ברוכים הבאים למונורפו Astro של {project}.",
409
+ getStartedMessage: "התחילו בעריכת apps/web/src/pages/index.astro.",
410
+ readDocs: "קריאת התיעוד",
411
+ getCustomDemo: "קבלת דמו מותאם",
412
+ },
413
+ } as const satisfies Record<TemplateLanguage, Record<string, string>>;
414
+
415
+ export const appUi = {
416
+ lang: CURRENT_LANGUAGE,
417
+ dir: RTL_LANGUAGES.includes(
418
+ CURRENT_LANGUAGE as (typeof RTL_LANGUAGES)[number],
419
+ )
420
+ ? "rtl"
421
+ : "ltr",
422
+ ...ui[CURRENT_LANGUAGE],
423
+ };
424
+ `
425
+ };
426
+ const TEMPLATE_PAGE_VARIANTS = [
427
+ {
428
+ matchers: [
429
+ "import { Button } from \"@/ui/button\";",
430
+ "Astro project.",
431
+ "Get started by editing src/pages/index.astro."
432
+ ],
433
+ i18nSource: `---
434
+ import Layout from "@/layouts/Layout.astro";
435
+ import { appUi } from "@/i18n/ui";
436
+ import { Button } from "@/ui/button";
437
+
438
+ const [welcomePrefix, welcomeSuffix = ""] =
439
+ appUi.welcomeMessage.split("{project}");
440
+ ---
441
+
442
+ <Layout>
443
+ <div
444
+ class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
445
+ >
446
+ <div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
447
+ <img src="/bejamas.svg" alt="bejamas/ui" class="w-8 dark:invert" />
448
+ <div class="space-y-2">
449
+ <p class="text-sm">
450
+ {welcomePrefix}<code
451
+ class="font-bold relative bg-muted rounded p-1"
452
+ >@bejamas/ui</code
453
+ >{welcomeSuffix}
454
+ </p>
455
+ <p class="text-sm">
456
+ {appUi.getStartedMessage}
457
+ </p>
458
+ </div>
459
+ <div class="flex gap-2">
460
+ <Button as="a" href="https://ui.bejamas.com/docs"
461
+ >{appUi.readDocs}</Button
462
+ >
463
+ <Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
464
+ >{appUi.getCustomDemo}</Button
465
+ >
466
+ </div>
467
+ </div>
468
+ </div>
469
+ </Layout>
470
+ `
471
+ },
472
+ {
473
+ matchers: [
474
+ "import { Button } from \"@repo/ui/components/button\";",
475
+ "width=\"32\"",
476
+ "Astro monorepo.",
477
+ "Get started by editing apps/web/src/pages/index.astro."
478
+ ],
479
+ i18nSource: `---
480
+ import Layout from "@/layouts/Layout.astro";
481
+ import { appUi } from "@/i18n/ui";
482
+ import { Button } from "@repo/ui/components/button";
483
+
484
+ const [welcomePrefix, welcomeSuffix = ""] =
485
+ appUi.welcomeMessage.split("{project}");
486
+ ---
487
+
488
+ <Layout>
489
+ <div
490
+ class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
491
+ >
492
+ <div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
493
+ <img
494
+ src="/bejamas.svg"
495
+ alt="bejamas/ui"
496
+ class="w-8 dark:invert"
497
+ width="32"
498
+ height="32"
499
+ alt="bejamas/ui logo"
500
+ />
501
+ <div class="space-y-2">
502
+ <p class="text-sm">
503
+ {welcomePrefix}<code
504
+ class="font-bold relative bg-muted rounded p-1"
505
+ >@bejamas/ui</code
506
+ >{welcomeSuffix}
507
+ </p>
508
+ <p class="text-sm">
509
+ {appUi.getStartedMessage}
510
+ </p>
511
+ </div>
512
+ <div class="flex gap-2">
513
+ <Button as="a" href="https://ui.bejamas.com/docs"
514
+ >{appUi.readDocs}</Button
515
+ >
516
+ <Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
517
+ >{appUi.getCustomDemo}</Button
518
+ >
519
+ </div>
520
+ </div>
521
+ </div>
522
+ </Layout>
523
+ `
524
+ },
525
+ {
526
+ matchers: [
527
+ "import { Button } from \"@repo/ui/components/button\";",
528
+ "<img src=\"/bejamas.svg\" alt=\"bejamas/ui\" class=\"w-8 dark:invert\" />",
529
+ "Astro monorepo.",
530
+ "Get started by editing apps/web/src/pages/index.astro."
531
+ ],
532
+ i18nSource: `---
533
+ import Layout from "@/layouts/Layout.astro";
534
+ import { appUi } from "@/i18n/ui";
535
+ import { Button } from "@repo/ui/components/button";
536
+
537
+ const [welcomePrefix, welcomeSuffix = ""] =
538
+ appUi.welcomeMessage.split("{project}");
539
+ ---
540
+
541
+ <Layout>
542
+ <div
543
+ class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
544
+ >
545
+ <div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
546
+ <img src="/bejamas.svg" alt="bejamas/ui" class="w-8 dark:invert" />
547
+ <div class="space-y-2">
548
+ <p class="text-sm">
549
+ {welcomePrefix}<code
550
+ class="font-bold relative bg-muted rounded p-1"
551
+ >@bejamas/ui</code
552
+ >{welcomeSuffix}
553
+ </p>
554
+ <p class="text-sm">
555
+ {appUi.getStartedMessage}
556
+ </p>
557
+ </div>
558
+ <div class="flex gap-2">
559
+ <Button as="a" href="https://ui.bejamas.com/docs"
560
+ >{appUi.readDocs}</Button
561
+ >
562
+ <Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
563
+ >{appUi.getCustomDemo}</Button
564
+ >
565
+ </div>
566
+ </div>
567
+ </div>
568
+ </Layout>
569
+ `
570
+ }
571
+ ];
572
+ function escapeRegExp(value) {
573
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
574
+ }
575
+ function compactCss(source) {
576
+ return source.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
577
+ }
578
+ function compactSource(source) {
579
+ return source.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
580
+ }
581
+ function stripLegacyCreateBlock(source) {
582
+ const fullBlockPattern = new RegExp(`\\n*${escapeRegExp(CREATE_BLOCK_START)}[\\s\\S]*?${escapeRegExp(CREATE_BLOCK_END)}\\n*`, "m");
583
+ const danglingBlockPattern = new RegExp(`\\n*${escapeRegExp(CREATE_BLOCK_START)}[\\s\\S]*$`, "m");
584
+ return compactCss(source.replace(fullBlockPattern, "\n\n").replace(danglingBlockPattern, "\n\n"));
585
+ }
586
+ function buildCssVarBlock(selector, vars) {
587
+ return [
588
+ `${selector} {`,
589
+ ...Object.entries(vars).map(([key, value]) => ` --${key}: ${value};`),
590
+ "}"
591
+ ].join("\n");
592
+ }
593
+ function replaceTopLevelBlock(source, selector, nextBlock) {
594
+ const pattern = new RegExp(`^${escapeRegExp(selector)}\\s*\\{[\\s\\S]*?^\\}`, "m");
595
+ if (pattern.test(source)) return source.replace(pattern, nextBlock);
596
+ const layerIndex = source.indexOf("@layer base");
597
+ if (layerIndex !== -1) return `${source.slice(0, layerIndex).trimEnd()}\n\n${nextBlock}\n\n${source.slice(layerIndex).trimStart()}`;
598
+ return `${source.trimEnd()}\n\n${nextBlock}\n`;
599
+ }
600
+ function upsertImports(source, imports) {
601
+ const cleanedLines = source.split("\n").filter((line) => {
602
+ const trimmed = line.trim();
603
+ if (MANAGED_TAILWIND_IMPORTS.has(trimmed)) return false;
604
+ return !trimmed.startsWith("@import \"@fontsource-variable/");
605
+ });
606
+ let insertAt = -1;
607
+ for (let index = 0; index < cleanedLines.length; index += 1) {
608
+ if (cleanedLines[index].trim().startsWith("@import ")) {
609
+ insertAt = index;
610
+ continue;
611
+ }
612
+ if (insertAt !== -1 && cleanedLines[index].trim() !== "") break;
613
+ }
614
+ const uniqueImports = imports.filter((line, index) => imports.indexOf(line) === index);
615
+ if (insertAt === -1) return compactCss([
616
+ ...uniqueImports,
617
+ "",
618
+ ...cleanedLines
619
+ ].join("\n"));
620
+ cleanedLines.splice(insertAt + 1, 0, ...uniqueImports);
621
+ return compactCss(cleanedLines.join("\n"));
622
+ }
623
+ function upsertManagedTailwindImport(source) {
624
+ const nextImport = resolveManagedTailwindImport(source);
625
+ const cleanedLines = source.split("\n").filter((line) => !MANAGED_TAILWIND_IMPORTS.has(line.trim()));
626
+ let insertAt = -1;
627
+ for (let index = 0; index < cleanedLines.length; index += 1) {
628
+ if (cleanedLines[index].trim().startsWith("@import ")) {
629
+ insertAt = index;
630
+ continue;
631
+ }
632
+ if (insertAt !== -1 && cleanedLines[index].trim() !== "") break;
633
+ }
634
+ if (insertAt === -1) return compactCss([
635
+ nextImport,
636
+ "",
637
+ ...cleanedLines
638
+ ].join("\n"));
639
+ cleanedLines.splice(insertAt + 1, 0, nextImport);
640
+ return compactCss(cleanedLines.join("\n"));
641
+ }
642
+ function resolveManagedTailwindImport(source) {
643
+ if (source.includes(SHADCN_TAILWIND_IMPORT)) return SHADCN_TAILWIND_IMPORT;
644
+ if (source.includes(BEJAMAS_TAILWIND_IMPORT)) return BEJAMAS_TAILWIND_IMPORT;
645
+ return BEJAMAS_TAILWIND_IMPORT;
646
+ }
647
+ function normalizeSourceForComparison(source) {
648
+ return source.replace(/\r\n/g, "\n").replace(/\s+/g, " ").trim();
649
+ }
650
+ function upsertFrontmatterImport(source, importLine) {
651
+ if (source.includes(importLine)) return source;
652
+ const frontmatterEnd = source.indexOf("\n---", 3);
653
+ if (source.startsWith("---\n") && frontmatterEnd !== -1) return `${source.slice(0, frontmatterEnd).trimEnd()}\n${importLine}${source.slice(frontmatterEnd)}`;
654
+ return `${importLine}\n${source}`;
655
+ }
656
+ function getTemplateI18nVariant(filepath) {
657
+ return filepath.includes(`${path.sep}apps${path.sep}web${path.sep}`) ? "monorepo" : "astro";
658
+ }
659
+ function buildTemplateI18nSource(variant, language) {
660
+ return compactSource(TEMPLATE_I18N_SOURCES[variant].replace("export const CURRENT_LANGUAGE: TemplateLanguage = \"en\";", `export const CURRENT_LANGUAGE: TemplateLanguage = "${language}";`));
661
+ }
662
+ function transformTemplateLayoutToI18n(source) {
663
+ if (source.includes(TEMPLATE_APP_UI_IMPORT)) return source;
664
+ let next = upsertFrontmatterImport(source, TEMPLATE_APP_UI_IMPORT);
665
+ next = next.replace(/<html([^>]*?)\slang=(\"[^\"]*\"|\{[^}]+\})([^>]*)>/, "<html$1$3>").replace(/<html([^>]*?)\sdir=(\"[^\"]*\"|\{[^}]+\})([^>]*)>/, "<html$1$3>");
666
+ next = next.replace(/<html([^>]*)>/, "<html$1 lang={appUi.lang} dir={appUi.dir}>");
667
+ next = next.replace(/<title>[\s\S]*?<\/title>/, "<title>{appUi.metadataTitle}</title>");
668
+ return compactSource(next);
669
+ }
670
+ function transformStarterPageToI18n(source) {
671
+ if (source.includes(TEMPLATE_APP_UI_IMPORT)) return source;
672
+ const normalized = normalizeSourceForComparison(source);
673
+ const match = TEMPLATE_PAGE_VARIANTS.find((variant) => variant.matchers.every((matcher) => normalized.includes(normalizeSourceForComparison(matcher))));
674
+ if (!match) return source;
675
+ return compactSource(match.i18nSource);
676
+ }
677
+ function upsertThemeInlineFont(source, fontVariable) {
678
+ const pattern = /@theme inline\s*\{[\s\S]*?\n\}/m;
679
+ if (!pattern.test(source)) {
680
+ const block = `@theme inline {\n${[fontVariable ? ` ${fontVariable}: var(${fontVariable});` : null, " --font-heading: var(--font-heading);"].filter(Boolean).join("\n")}\n}`;
681
+ const customVariantIndex = source.indexOf("@custom-variant");
682
+ if (customVariantIndex !== -1) {
683
+ const nextLineIndex = source.indexOf("\n", customVariantIndex);
684
+ return `${source.slice(0, nextLineIndex + 1).trimEnd()}\n\n${block}\n\n${source.slice(nextLineIndex + 1).trimStart()}`;
685
+ }
686
+ return `${block}\n\n${source.trimStart()}`;
687
+ }
688
+ return source.replace(pattern, (block) => {
689
+ 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");
690
+ const declarations = [fontVariable ? ` ${fontVariable}: var(${fontVariable});` : null, " --font-heading: var(--font-heading);"].filter(Boolean).join("\n");
691
+ return withoutManagedFonts.replace("@theme inline {\n", `@theme inline {\n${declarations}\n`);
692
+ });
693
+ }
694
+ function upsertBaseLayerHtmlFont(source, fontClass) {
695
+ const pattern = /@layer base\s*\{[\s\S]*?\n\}/m;
696
+ 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`;
697
+ return source.replace(pattern, (block) => {
698
+ 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}`);
699
+ });
700
+ }
701
+ function transformDesignSystemCss(source, config, themeVars) {
702
+ const effectiveThemeVars = themeVars ?? buildRegistryTheme(config).cssVars;
703
+ const rootVars = {
704
+ ...Object.fromEntries(Object.entries(effectiveThemeVars.theme ?? {}).filter(([key]) => key !== "bejamas-font-family")),
705
+ ...effectiveThemeVars.light ?? {}
706
+ };
707
+ const darkVars = { ...effectiveThemeVars.dark ?? {} };
708
+ const font = getFontValue(config.font);
709
+ const tailwindImport = resolveManagedTailwindImport(source);
710
+ let next = stripLegacyCreateBlock(source);
711
+ next = upsertImports(next, [tailwindImport]);
712
+ next = replaceTopLevelBlock(next, ":root", buildCssVarBlock(":root", rootVars));
713
+ next = replaceTopLevelBlock(next, ".dark", buildCssVarBlock(".dark", darkVars));
714
+ next = upsertThemeInlineFont(next, font?.font.variable);
715
+ if (font) next = upsertBaseLayerHtmlFont(next, fontClassFromCssVariable(font.font.variable));
716
+ return compactCss(next);
717
+ }
718
+ function transformAstroManagedFontCss(source, fontVariable) {
719
+ const tailwindImport = resolveManagedTailwindImport(source);
720
+ let next = stripLegacyCreateBlock(source);
721
+ next = upsertImports(next, [tailwindImport]);
722
+ next = upsertThemeInlineFont(next, fontVariable);
723
+ if (fontVariable) next = upsertBaseLayerHtmlFont(next, fontClassFromCssVariable(fontVariable));
724
+ return compactCss(next);
725
+ }
726
+ function transformManagedTailwindImportCss(source) {
727
+ return upsertManagedTailwindImport(source);
728
+ }
729
+ async function patchComponentsJson(filepath, config) {
730
+ const current = await fs.readJson(filepath);
731
+ const { rsc: _rsc, tsx: _tsx,...normalizedCurrent } = current;
732
+ const next = {
733
+ ...normalizedCurrent,
734
+ $schema: BEJAMAS_COMPONENTS_SCHEMA_URL,
735
+ style: getStyleId(config.style),
736
+ iconLibrary: config.iconLibrary,
737
+ rtl: config.rtl,
738
+ tailwind: {
739
+ ...current.tailwind ?? {},
740
+ baseColor: config.baseColor,
741
+ cssVariables: true
742
+ }
743
+ };
744
+ await fs.writeJson(filepath, next, { spaces: 2 });
745
+ }
746
+ async function patchCssFileWithTheme(filepath, config, themeVars) {
747
+ const next = transformDesignSystemCss(await fs.readFile(filepath, "utf8"), config, themeVars);
748
+ await fs.writeFile(filepath, next, "utf8");
749
+ }
750
+ async function patchCssFileWithAstroFont(filepath, fontVariable) {
751
+ const next = transformAstroManagedFontCss(await fs.readFile(filepath, "utf8"), fontVariable);
752
+ await fs.writeFile(filepath, next, "utf8");
753
+ }
754
+ async function patchCssFileManagedTailwindImport(filepath) {
755
+ const next = transformManagedTailwindImportCss(await fs.readFile(filepath, "utf8"));
756
+ await fs.writeFile(filepath, next, "utf8");
757
+ }
758
+ async function syncManagedTailwindCss(projectPath) {
759
+ const componentJsonFiles = await fg("**/components.json", {
760
+ cwd: projectPath,
761
+ absolute: true,
762
+ ignore: ["**/node_modules/**"]
763
+ });
764
+ await Promise.all(componentJsonFiles.map(async (componentJsonPath) => {
765
+ const cssRelativePath = (await fs.readJson(componentJsonPath))?.tailwind?.css;
766
+ if (typeof cssRelativePath !== "string" || cssRelativePath.length === 0) return;
767
+ const cssPath = path.resolve(path.dirname(componentJsonPath), cssRelativePath);
768
+ if (!await fs.pathExists(cssPath)) return;
769
+ await patchCssFileManagedTailwindImport(cssPath);
770
+ }));
771
+ }
772
+ async function syncAstroManagedFontCss(projectPath, fontVariable) {
773
+ const componentJsonFiles = await fg("**/components.json", {
774
+ cwd: projectPath,
775
+ ignore: [
776
+ "**/node_modules/**",
777
+ "**/dist/**",
778
+ "**/.astro/**"
779
+ ]
780
+ });
781
+ const cssFiles = /* @__PURE__ */ new Set();
782
+ for (const relativePath of componentJsonFiles) {
783
+ const absolutePath = path.resolve(projectPath, relativePath);
784
+ const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
785
+ if (typeof cssPath === "string" && cssPath.length > 0) cssFiles.add(path.resolve(path.dirname(absolutePath), cssPath));
786
+ }
787
+ await Promise.all(Array.from(cssFiles).map((filepath) => patchCssFileWithAstroFont(filepath, fontVariable)));
788
+ }
789
+ async function patchTemplateI18nFile(filepath, config) {
790
+ if (!config.rtl) return;
791
+ const nextLanguage = getDocumentLanguage(config);
792
+ const nextSource = buildTemplateI18nSource(getTemplateI18nVariant(filepath), nextLanguage);
793
+ if (!await fs.pathExists(filepath)) {
794
+ await fs.ensureDir(path.dirname(filepath));
795
+ await fs.writeFile(filepath, nextSource, "utf8");
796
+ return;
797
+ }
798
+ const current = await fs.readFile(filepath, "utf8");
799
+ const next = current.includes("export const CURRENT_LANGUAGE") ? current.replace(/export const CURRENT_LANGUAGE: TemplateLanguage = "[^"]+";/, `export const CURRENT_LANGUAGE: TemplateLanguage = "${nextLanguage}";`) : nextSource;
800
+ if (next !== current) await fs.writeFile(filepath, next, "utf8");
801
+ }
802
+ async function patchLayoutFile(filepath, config) {
803
+ if (!await fs.pathExists(filepath)) return;
804
+ const current = await fs.readFile(filepath, "utf8");
805
+ if (current.includes("from \"@/i18n/ui\"")) return;
806
+ if (config.rtl) {
807
+ const next$1 = transformTemplateLayoutToI18n(current);
808
+ if (next$1 !== current) await fs.writeFile(filepath, next$1, "utf8");
809
+ return;
810
+ }
811
+ const nextLanguage = getDocumentLanguage(config);
812
+ const nextDirection = getDocumentDirection(config);
813
+ let next = current.replace(/<html([^>]*?)lang="[^"]*"([^>]*)>/, "<html$1$2>").replace(/<html([^>]*?)dir="[^"]*"([^>]*)>/, "<html$1$2>");
814
+ next = next.replace(/<html([^>]*)>/, `<html$1 lang="${nextLanguage}"${config.rtl ? ` dir="${nextDirection}"` : ""}>`);
815
+ if (next !== current) await fs.writeFile(filepath, next, "utf8");
816
+ }
817
+ async function patchStarterPageFile(filepath, config) {
818
+ if (!config.rtl || !await fs.pathExists(filepath)) return;
819
+ const current = await fs.readFile(filepath, "utf8");
820
+ const next = transformStarterPageToI18n(current);
821
+ if (next !== current) await fs.writeFile(filepath, next, "utf8");
822
+ }
823
+ async function applyDesignSystemToProject(projectPath, config, options = {}) {
824
+ const componentJsonFiles = await fg("**/components.json", {
825
+ cwd: projectPath,
826
+ ignore: [
827
+ "**/node_modules/**",
828
+ "**/dist/**",
829
+ "**/.astro/**"
830
+ ]
831
+ });
832
+ const cssFiles = /* @__PURE__ */ new Set();
833
+ for (const relativePath of componentJsonFiles) {
834
+ const absolutePath = path.resolve(projectPath, relativePath);
835
+ await patchComponentsJson(absolutePath, config);
836
+ const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
837
+ if (typeof cssPath === "string" && cssPath.length > 0) {
838
+ const absoluteCssPath = path.resolve(path.dirname(absolutePath), cssPath);
839
+ cssFiles.add(absoluteCssPath);
840
+ }
841
+ }
842
+ await Promise.all(Array.from(cssFiles).map((filepath) => patchCssFileWithTheme(filepath, config, options.themeVars)));
843
+ await Promise.all([path.resolve(projectPath, "src/i18n/ui.ts"), path.resolve(projectPath, "apps/web/src/i18n/ui.ts")].map((filepath) => patchTemplateI18nFile(filepath, config)));
844
+ await Promise.all([path.resolve(projectPath, "src/layouts/Layout.astro"), path.resolve(projectPath, "apps/web/src/layouts/Layout.astro")].map((filepath) => patchLayoutFile(filepath, config)));
845
+ await Promise.all([path.resolve(projectPath, "src/pages/index.astro"), path.resolve(projectPath, "apps/web/src/pages/index.astro")].map((filepath) => patchStarterPageFile(filepath, config)));
846
+ const managedFonts = [toManagedAstroFont(config.font), config.fontHeading !== "inherit" ? toManagedAstroFont(`font-heading-${config.fontHeading}`) : null].filter((font) => font !== null);
847
+ const managedFont = managedFonts.find((font) => font.cssVariable !== "--font-heading");
848
+ if (managedFonts.length > 0 && managedFont) {
849
+ await syncAstroFontsInProject(projectPath, managedFonts, managedFont.cssVariable);
850
+ await cleanupAstroFontPackages(projectPath);
851
+ }
852
+ }
853
+
854
+ //#endregion
855
+ //#region src/utils/icon-transform.ts
856
+ const FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---/;
857
+ function rewriteAstroIcons(content, iconLibrary) {
858
+ if (!(iconLibrary in ICON_LIBRARY_COLLECTIONS)) return content;
859
+ const library = iconLibrary;
860
+ const shouldRewriteLucideImports = library !== "lucide";
861
+ const frontmatterMatch = content.match(FRONTMATTER_PATTERN);
862
+ if (!frontmatterMatch) return content;
863
+ let frontmatter = frontmatterMatch[1];
864
+ let body = content.slice(frontmatterMatch[0].length);
865
+ const iconUsages = [];
866
+ const semanticIconImports = [];
867
+ if (shouldRewriteLucideImports) {
868
+ frontmatter = frontmatter.replace(/^\s*import\s+([A-Za-z0-9_$]+)\s+from\s+["']@lucide\/astro\/icons\/([^"']+)["'];?\s*$/gm, (full, localName, iconPath) => {
869
+ const semanticName = getSemanticIconNameFromLucidePath(iconPath);
870
+ if (!semanticName) return full;
871
+ iconUsages.push({
872
+ localName,
873
+ semanticName
874
+ });
875
+ return "";
876
+ });
877
+ frontmatter = frontmatter.replace(/import\s+\{([^}]*)\}\s+from\s+["']@lucide\/astro["'];?/g, (full, importsBlock) => {
878
+ const keptImports = [];
879
+ for (const rawImport of importsBlock.split(",")) {
880
+ const trimmedImport = rawImport.trim();
881
+ if (!trimmedImport) continue;
882
+ const aliasMatch = trimmedImport.match(/^([A-Za-z0-9_$]+)(?:\s+as\s+([A-Za-z0-9_$]+))?$/);
883
+ if (!aliasMatch) {
884
+ keptImports.push(trimmedImport);
885
+ continue;
886
+ }
887
+ const [, importedName, localAlias] = aliasMatch;
888
+ const semanticName = getSemanticIconNameFromLucideExport(importedName);
889
+ if (!semanticName) {
890
+ keptImports.push(trimmedImport);
891
+ continue;
892
+ }
893
+ iconUsages.push({
894
+ localName: localAlias ?? importedName,
895
+ semanticName
896
+ });
897
+ }
898
+ if (!keptImports.length) return "";
899
+ return `import { ${keptImports.join(", ")} } from "@lucide/astro";`;
900
+ });
901
+ }
902
+ frontmatter = frontmatter.replace(/^\s*import\s+([A-Za-z0-9_$]+)\s+from\s+["'][^"']*SemanticIcon\.astro["'];?\s*$/gm, (_full, localName) => {
903
+ semanticIconImports.push(localName);
904
+ return "";
905
+ });
906
+ if (!iconUsages.length && !semanticIconImports.length) return content;
907
+ for (const { localName, semanticName } of iconUsages.sort((left, right) => right.localName.length - left.localName.length)) {
908
+ const selfClosingPattern = new RegExp(`<${localName}(\\s[^>]*?)?\\s*/>`, "g");
909
+ const pairedPattern = new RegExp(`<${localName}(\\s[^>]*?)?>([\\s\\S]*?)<\\/${localName}>`, "g");
910
+ body = body.replace(selfClosingPattern, (_full, attributeString = "") => renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString));
911
+ body = body.replace(pairedPattern, (_full, attributeString = "") => renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString));
912
+ }
913
+ for (const localName of semanticIconImports.sort((left, right) => right.length - left.length)) {
914
+ const selfClosingPattern = new RegExp(`<${localName}(\\s[^>]*?)?\\s*/>`, "g");
915
+ const pairedPattern = new RegExp(`<${localName}(\\s[^>]*?)?>([\\s\\S]*?)<\\/${localName}>`, "g");
916
+ body = body.replace(selfClosingPattern, (full, attributeString = "") => renderSemanticIconUsage(full, library, attributeString));
917
+ body = body.replace(pairedPattern, (full, attributeString = "") => renderSemanticIconUsage(full, library, attributeString));
918
+ }
919
+ return `---\n${frontmatter.split("\n").filter((line, index, lines) => {
920
+ if (line.trim().length > 0) return true;
921
+ return lines[index - 1]?.trim().length > 0;
922
+ }).join("\n").trim()}\n---${body}`;
923
+ }
924
+ function renderSemanticIconUsage(original, library, attributeString = "") {
925
+ const semanticName = attributeString.match(/\sname=(["'])([^"']+)\1/)?.[2];
926
+ if (!semanticName || !SEMANTIC_ICON_NAMES.includes(semanticName)) return original;
927
+ return renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString.replace(/\sname=(["'])[^"']+\1/g, "").replace(/\slibrary=(["'])[^"']+\1/g, "").replace(/\sdata-icon-library=(["'])[^"']+\1/g, "").trim());
928
+ }
929
+
930
+ //#endregion
931
+ //#region src/utils/menu-transform.ts
932
+ const TRANSLUCENT_CLASSES = [
933
+ "animate-none!",
934
+ "relative",
935
+ "bg-popover/70",
936
+ "before:pointer-events-none",
937
+ "before:absolute",
938
+ "before:inset-0",
939
+ "before:-z-1",
940
+ "before:rounded-[inherit]",
941
+ "before:backdrop-blur-2xl",
942
+ "before:backdrop-saturate-150",
943
+ "**:data-[slot$=-item]:focus:bg-foreground/10",
944
+ "**:data-[slot$=-item]:data-highlighted:bg-foreground/10",
945
+ "**:data-[slot$=-separator]:bg-foreground/5",
946
+ "**:data-[slot$=-trigger]:focus:bg-foreground/10",
947
+ "**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10!",
948
+ "**:data-[variant=destructive]:focus:bg-foreground/10!",
949
+ "**:data-[variant=destructive]:text-accent-foreground!",
950
+ "**:data-[variant=destructive]:**:text-accent-foreground!"
951
+ ];
952
+ function transformMenuTokens(tokens, menuColor) {
953
+ const transformed = [];
954
+ for (const token of tokens) {
955
+ if (token === "cn-menu-target") {
956
+ if (menuColor === "inverted") transformed.push("dark");
957
+ else if (menuColor === "default-translucent") transformed.push(...TRANSLUCENT_CLASSES);
958
+ else if (menuColor === "inverted-translucent") transformed.push("dark", ...TRANSLUCENT_CLASSES);
959
+ continue;
960
+ }
961
+ if (token === "cn-menu-translucent") {
962
+ if (menuColor === "default-translucent" || menuColor === "inverted-translucent") transformed.push(...TRANSLUCENT_CLASSES);
963
+ continue;
964
+ }
965
+ transformed.push(token);
966
+ }
967
+ return Array.from(new Set(transformed));
968
+ }
969
+ function rewriteAstroMenus(content, menuColor) {
970
+ if (!content.includes("cn-menu-target") && !content.includes("cn-menu-translucent")) return content;
971
+ return content.replace(/(["'])([^"']*\bcn-menu-(?:target|translucent)\b[^"']*)\1/g, (_match, quote, classes) => {
972
+ return `${quote}${transformMenuTokens(String(classes).split(/\s+/).filter(Boolean), menuColor).join(" ")}${quote}`;
973
+ });
974
+ }
975
+
976
+ //#endregion
977
+ //#region src/utils/astro-imports.ts
978
+ function updateImportAliases(moduleSpecifier, config, isRemote = false) {
979
+ if (!moduleSpecifier.startsWith("@/") && !isRemote) return moduleSpecifier;
980
+ let specifier = moduleSpecifier;
981
+ if (isRemote && specifier.startsWith("@/")) specifier = specifier.replace(/^@\//, `@/registry/${config.style || "bejamas-juno"}/`);
982
+ if (!specifier.startsWith("@/registry/")) {
983
+ const alias = config.aliases.components.split("/")[0];
984
+ return specifier.replace(/^@\//, `${alias}/`);
985
+ }
986
+ if (specifier.match(/^@\/registry\/(.+)\/ui/)) return specifier.replace(/^@\/registry\/(.+)\/ui/, config.aliases.ui ?? `${config.aliases.components}/ui`);
987
+ if (config.aliases.components && specifier.match(/^@\/registry\/(.+)\/components/)) return specifier.replace(/^@\/registry\/(.+)\/components/, config.aliases.components);
988
+ if (config.aliases.lib && specifier.match(/^@\/registry\/(.+)\/lib/)) return specifier.replace(/^@\/registry\/(.+)\/lib/, config.aliases.lib);
989
+ if (config.aliases.hooks && specifier.match(/^@\/registry\/(.+)\/hooks/)) return specifier.replace(/^@\/registry\/(.+)\/hooks/, config.aliases.hooks);
990
+ return specifier.replace(/^@\/registry\/[^/]+/, config.aliases.components);
991
+ }
992
+ function rewriteAstroImports(content, config) {
993
+ let updated = content;
994
+ const utilsAlias = config.aliases?.utils;
995
+ const utilsImport = `${typeof utilsAlias === "string" && utilsAlias.includes("/") ? utilsAlias.split("/")[0] : "@"}/lib/utils`;
996
+ updated = updated.replace(/import\s+([\s\S]*?)\s+from\s+["']([^"']+)["']/g, (full, importsPart, specifier) => {
997
+ const next = updateImportAliases(specifier, config, false);
998
+ let finalSpec = next;
999
+ 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;
1000
+ if (finalSpec === specifier) return full;
1001
+ return full.replace(specifier, finalSpec);
1002
+ });
1003
+ updated = updated.replace(/import\s+["']([^"']+)["']/g, (full, specifier) => {
1004
+ const next = updateImportAliases(specifier, config, false);
1005
+ if (next === specifier) return full;
1006
+ return full.replace(specifier, next);
1007
+ });
1008
+ updated = rewriteAstroMenus(updated, config.menuColor);
1009
+ return rewriteAstroIcons(updated, config.iconLibrary);
1010
+ }
1011
+ async function fixAstroImports(cwd, isVerbose) {
1012
+ const config = await getConfig(cwd);
1013
+ if (!config) return;
1014
+ const searchRoots = new Set([config.resolvedPaths.components, config.resolvedPaths.ui]);
1015
+ for (const root of Array.from(searchRoots)) {
1016
+ if (!root) continue;
1017
+ const astroFiles = await fg("**/*.astro", {
1018
+ cwd: root,
1019
+ absolute: true,
1020
+ dot: false
1021
+ });
1022
+ for (const filePath of astroFiles) {
1023
+ const original = await fs$1.readFile(filePath, "utf8");
1024
+ const rewritten = rewriteAstroImports(original, config);
1025
+ if (rewritten === original) continue;
1026
+ await fs$1.writeFile(filePath, rewritten, "utf8");
1027
+ if (isVerbose) logger.info(`[bejamas-ui] fixed imports in ${path.relative(cwd, filePath)}`);
1028
+ }
1029
+ }
44
1030
  }
45
1031
 
46
1032
  //#endregion
@@ -154,7 +1140,130 @@ const TEMPLATES = {
154
1140
  "astro-monorepo": "astro-monorepo",
155
1141
  "astro-with-component-docs-monorepo": "astro-with-component-docs-monorepo"
156
1142
  };
157
- const MONOREPO_TEMPLATE_URL = "https://codeload.github.com/bejamas/ui/tar.gz/main";
1143
+ const __filename = fileURLToPath(import.meta.url);
1144
+ const __dirname = path$1.dirname(__filename);
1145
+ const TEMPLATE_DIRNAME = {
1146
+ astro: "astro",
1147
+ "astro-monorepo": "monorepo-astro",
1148
+ "astro-with-component-docs-monorepo": "monorepo-astro-with-docs"
1149
+ };
1150
+ const TEMPLATE_EXCLUDED_BASENAMES = new Set(["node_modules", ".astro"]);
1151
+ const DEFAULT_TEMPLATE_REPOSITORY = "bejamas/ui";
1152
+ const DEFAULT_TEMPLATE_REF = "main";
1153
+ function resolveLocalTemplatesDir(env = process.env) {
1154
+ const override = env.BEJAMAS_LOCAL_TEMPLATES_DIR?.trim();
1155
+ if (override) return path$1.resolve(override);
1156
+ for (const relativePath of [
1157
+ "../../../../templates",
1158
+ "../../../templates",
1159
+ "../../templates"
1160
+ ]) {
1161
+ const candidate = path$1.resolve(__dirname, relativePath);
1162
+ if (fs.existsSync(candidate)) return candidate;
1163
+ }
1164
+ return path$1.resolve(__dirname, "../../../../templates");
1165
+ }
1166
+ const LOCAL_TEMPLATES_DIR = resolveLocalTemplatesDir();
1167
+ function resolvePackageMetadataPath() {
1168
+ for (const relativePath of ["../package.json", "../../package.json"]) {
1169
+ const candidate = path$1.resolve(__dirname, relativePath);
1170
+ if (fs.existsSync(candidate)) return candidate;
1171
+ }
1172
+ return path$1.resolve(__dirname, "../package.json");
1173
+ }
1174
+ function readPackageMetadata() {
1175
+ try {
1176
+ return fs.readJsonSync(resolvePackageMetadataPath());
1177
+ } catch {
1178
+ return {};
1179
+ }
1180
+ }
1181
+ const BEJAMAS_PACKAGE_METADATA = readPackageMetadata();
1182
+ function shouldCopyTemplateEntry(source) {
1183
+ return !TEMPLATE_EXCLUDED_BASENAMES.has(path$1.basename(source));
1184
+ }
1185
+ async function copyTemplateIntoProject(templateSource, projectPath) {
1186
+ await fs.copy(templateSource, projectPath, { filter: shouldCopyTemplateEntry });
1187
+ }
1188
+ function resolveRemoteTemplateRefs(metadata = BEJAMAS_PACKAGE_METADATA, env = process.env) {
1189
+ const refs = [
1190
+ env.BEJAMAS_TEMPLATE_REF?.trim(),
1191
+ metadata.gitHead?.trim(),
1192
+ metadata.version && !metadata.version.includes("-") ? `bejamas@${metadata.version}` : void 0,
1193
+ DEFAULT_TEMPLATE_REF
1194
+ ].filter((ref) => Boolean(ref));
1195
+ return [...new Set(refs)];
1196
+ }
1197
+ function buildTemplateArchiveUrl(ref, repository = DEFAULT_TEMPLATE_REPOSITORY) {
1198
+ return `https://api.github.com/repos/${repository}/tarball/${encodeURIComponent(ref)}`;
1199
+ }
1200
+ function getGitHubRequestHeaders(env = process.env) {
1201
+ const headers = { "User-Agent": `bejamas/${BEJAMAS_PACKAGE_METADATA.version ?? "dev"}` };
1202
+ const token = env.BEJAMAS_GITHUB_TOKEN?.trim() || env.GITHUB_TOKEN?.trim();
1203
+ if (token) headers.Authorization = `Bearer ${token}`;
1204
+ return headers;
1205
+ }
1206
+ async function downloadTemplateArchive(ref, targetDir) {
1207
+ const archiveUrl = buildTemplateArchiveUrl(ref);
1208
+ const archivePath = path$1.join(targetDir, "repo.tar.gz");
1209
+ const response = await fetch(archiveUrl, {
1210
+ headers: getGitHubRequestHeaders(),
1211
+ signal: AbortSignal.timeout(3e4)
1212
+ });
1213
+ if (!response.ok) throw new Error(`GitHub returned ${response.status} ${response.statusText} for ${ref}.`);
1214
+ await fs.ensureDir(targetDir);
1215
+ await fs.outputFile(archivePath, Buffer.from(await response.arrayBuffer()));
1216
+ await tar.x({
1217
+ cwd: targetDir,
1218
+ file: archivePath,
1219
+ strict: true
1220
+ });
1221
+ const extractedRoot = (await fs.readdir(targetDir)).filter((entry) => entry !== "repo.tar.gz").find((entry) => fs.statSync(path$1.join(targetDir, entry)).isDirectory());
1222
+ if (!extractedRoot) throw new Error(`Downloaded archive for ${ref} did not extract correctly.`);
1223
+ return path$1.join(targetDir, extractedRoot);
1224
+ }
1225
+ async function copyTemplateFromGitHub(templateDirName, projectPath) {
1226
+ const refs = resolveRemoteTemplateRefs();
1227
+ const failures = [];
1228
+ for (const ref of refs) {
1229
+ const tempDir = path$1.join(os.tmpdir(), `bejamas-template-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
1230
+ try {
1231
+ const extractedRoot = await downloadTemplateArchive(ref, tempDir);
1232
+ const templateSource = path$1.join(extractedRoot, "templates", templateDirName);
1233
+ if (!await fs.pathExists(templateSource)) throw new Error(`Template templates/${templateDirName} was not found.`);
1234
+ await copyTemplateIntoProject(templateSource, projectPath);
1235
+ return ref;
1236
+ } catch (error) {
1237
+ failures.push(`${ref}: ${error instanceof Error ? error.message : String(error)}`);
1238
+ } finally {
1239
+ await fs.remove(tempDir);
1240
+ }
1241
+ }
1242
+ throw new Error([
1243
+ `Unable to download templates/${templateDirName} from GitHub.`,
1244
+ `Tried refs: ${refs.join(", ")}.`,
1245
+ failures.join("\n")
1246
+ ].join("\n"));
1247
+ }
1248
+ async function applyLocalPackageOverrides(projectPath) {
1249
+ const bejamasPackageOverride = process.env.BEJAMAS_PACKAGE_OVERRIDE;
1250
+ if (!bejamasPackageOverride) return;
1251
+ const packageJsonPaths = await fg("**/package.json", {
1252
+ cwd: projectPath,
1253
+ absolute: true,
1254
+ ignore: ["**/node_modules/**"]
1255
+ });
1256
+ const normalizedOverride = bejamasPackageOverride.replace(/\\/g, "/");
1257
+ await Promise.all(packageJsonPaths.map(async (packageJsonPath) => {
1258
+ const packageJson = await fs.readJson(packageJsonPath);
1259
+ let changed = false;
1260
+ for (const field of ["dependencies", "devDependencies"]) if (packageJson[field]?.bejamas) {
1261
+ packageJson[field].bejamas = `file:${normalizedOverride}`;
1262
+ changed = true;
1263
+ }
1264
+ if (changed) await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
1265
+ }));
1266
+ }
158
1267
  async function createProject(options) {
159
1268
  options = {
160
1269
  srcDir: false,
@@ -207,7 +1316,7 @@ async function createProject(options) {
207
1316
  logger.break();
208
1317
  process.exit(1);
209
1318
  }
210
- if (fs.existsSync(path.resolve(options.cwd, projectName, "package.json"))) {
1319
+ if (fs.existsSync(path$1.resolve(options.cwd, projectName, "package.json"))) {
211
1320
  logger.break();
212
1321
  logger.error(`A project with the name ${highlighter.info(projectName)} already exists.`);
213
1322
  logger.error(`Please choose a different name and try again.`);
@@ -227,40 +1336,17 @@ async function createProject(options) {
227
1336
  }
228
1337
  async function createProjectFromTemplate(projectPath, options) {
229
1338
  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"
234
- };
235
1339
  try {
236
1340
  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}`);
1341
+ const templateDirName = TEMPLATE_DIRNAME[options.templateKey];
1342
+ const templateSource = path$1.resolve(LOCAL_TEMPLATES_DIR, templateDirName);
1343
+ if (await fs.pathExists(templateSource)) await copyTemplateIntoProject(templateSource, projectPath);
1344
+ else {
1345
+ createSpinner.text = `Downloading the ${highlighter.info(options.templateKey)} template from GitHub.`;
1346
+ await copyTemplateFromGitHub(templateDirName, projectPath);
248
1347
  }
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);
263
- await fs.remove(templatePath);
1348
+ await removeEmptyTemplateI18nDirs(projectPath);
1349
+ await applyLocalPackageOverrides(projectPath);
264
1350
  await execa(options.packageManager, ["install"], { cwd: projectPath });
265
1351
  try {
266
1352
  const { stdout } = await execa("git", ["rev-parse", "--is-inside-work-tree"], { cwd: projectPath });
@@ -273,51 +1359,295 @@ async function createProjectFromTemplate(projectPath, options) {
273
1359
  "Initial commit"
274
1360
  ], { cwd: projectPath });
275
1361
  }
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);
1362
+ } catch (_) {}
1363
+ createSpinner?.succeed("Creating a new project from template.");
1364
+ } catch (error) {
1365
+ createSpinner?.fail("Something went wrong creating a new project from template.");
1366
+ handleError(error);
1367
+ }
1368
+ }
1369
+ async function removeEmptyTemplateI18nDirs(projectPath) {
1370
+ const candidates = [path$1.resolve(projectPath, "src/i18n"), path$1.resolve(projectPath, "apps/web/src/i18n")];
1371
+ await Promise.all(candidates.map(async (candidate) => {
1372
+ if (!await fs.pathExists(candidate)) return;
1373
+ if ((await fs.readdir(candidate)).length === 0) await fs.remove(candidate);
1374
+ }));
1375
+ }
1376
+
1377
+ //#endregion
1378
+ //#region src/utils/installed-ui-components.ts
1379
+ function toRegistryName(filename) {
1380
+ return filename.replace(/\.[^.]+$/, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1381
+ }
1382
+ async function pathExists$1(filepath) {
1383
+ try {
1384
+ await promises.access(filepath);
1385
+ return true;
1386
+ } catch {
1387
+ return false;
1388
+ }
1389
+ }
1390
+ async function detectInstalledUiComponents(uiDir) {
1391
+ if (!await pathExists$1(uiDir)) return [];
1392
+ const entries = await promises.readdir(uiDir, { withFileTypes: true });
1393
+ const components = /* @__PURE__ */ new Set();
1394
+ for (const entry of entries) {
1395
+ if (entry.name.startsWith(".")) continue;
1396
+ const entryPath = path.resolve(uiDir, entry.name);
1397
+ if (entry.isDirectory()) {
1398
+ 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);
1399
+ continue;
1400
+ }
1401
+ if (entry.isFile() && /\.(astro|tsx|jsx)$/.test(entry.name) && !entry.name.startsWith("index.")) components.add(toRegistryName(entry.name));
1402
+ }
1403
+ return Array.from(components).sort();
1404
+ }
1405
+ async function getInstalledUiComponents(cwd) {
1406
+ const config = await getConfig(cwd);
1407
+ if (!config) return [];
1408
+ return detectInstalledUiComponents(config.resolvedPaths.ui);
1409
+ }
1410
+
1411
+ //#endregion
1412
+ //#region src/utils/reorganize-components.ts
1413
+ async function fetchRegistryItem(componentName, registryUrl, style = "bejamas-juno") {
1414
+ const url = `${registryUrl}/styles/${style}/${componentName}.json`;
1415
+ try {
1416
+ const response = await fetch(url);
1417
+ if (!response.ok) {
1418
+ const fallbackUrl = `${registryUrl}/${componentName}.json`;
1419
+ const fallbackResponse = await fetch(fallbackUrl);
1420
+ if (!fallbackResponse.ok) return null;
1421
+ return await fallbackResponse.json();
1422
+ }
1423
+ return await response.json();
1424
+ } catch {
1425
+ return null;
1426
+ }
1427
+ }
1428
+ function getSubfolderFromPaths(files) {
1429
+ if (!files || files.length === 0) return null;
1430
+ const uiFiles = files.filter((file) => file.type === "registry:ui");
1431
+ if (uiFiles.length < 2) return null;
1432
+ const subfolders = /* @__PURE__ */ new Set();
1433
+ for (const file of uiFiles) {
1434
+ const parts = file.path.split("/");
1435
+ const uiIndex = parts.indexOf("ui");
1436
+ if (uiIndex !== -1 && parts.length > uiIndex + 2) subfolders.add(parts[uiIndex + 1]);
1437
+ }
1438
+ if (subfolders.size === 1) return Array.from(subfolders)[0];
1439
+ if (uiFiles.length > 0) {
1440
+ const dirname$1 = path.dirname(uiFiles[0].path);
1441
+ const folderName = path.basename(dirname$1);
1442
+ if (folderName && folderName !== "ui") return folderName;
1443
+ }
1444
+ return null;
1445
+ }
1446
+ async function pathExists(filePath) {
1447
+ try {
1448
+ await fs$1.access(filePath);
1449
+ return true;
1450
+ } catch {
1451
+ return false;
1452
+ }
1453
+ }
1454
+ function resolveShadcnUiRelativePath(filePath, uiDir) {
1455
+ const normalizedFilePath = filePath.replace(/^\/|\/$/g, "");
1456
+ const lastTargetSegment = path.basename(uiDir.replace(/^\/|\/$/g, ""));
1457
+ if (!lastTargetSegment) return path.basename(normalizedFilePath);
1458
+ const fileSegments = normalizedFilePath.split("/");
1459
+ const commonDirIndex = fileSegments.findIndex((segment) => segment === lastTargetSegment);
1460
+ if (commonDirIndex === -1) return fileSegments[fileSegments.length - 1];
1461
+ return fileSegments.slice(commonDirIndex + 1).join("/");
1462
+ }
1463
+ /**
1464
+ * Current upstream shadcn workspace installs flatten `ui/foo/Bar.astro` when
1465
+ * the resolved ui target ends in a segment like `components` instead of `ui`.
1466
+ * We keep reorganization only for that compatibility case.
1467
+ */
1468
+ function shouldReorganizeRegistryUiFiles(files, uiDir) {
1469
+ if (!uiDir) return false;
1470
+ if (!getSubfolderFromPaths(files)) return false;
1471
+ return files.filter((file) => file.type === "registry:ui").some((file) => {
1472
+ return !resolveShadcnUiRelativePath(file.path, uiDir).includes("/");
1473
+ });
1474
+ }
1475
+ /**
1476
+ * Reorganizes one multi-file registry:ui item from flat output into its
1477
+ * expected subfolder. This is the filesystem-level compatibility shim.
1478
+ */
1479
+ async function reorganizeRegistryUiFiles(files, uiDir, verbose, overwriteExisting = false) {
1480
+ const result = {
1481
+ totalMoved: 0,
1482
+ movedFiles: [],
1483
+ skippedFiles: []
1484
+ };
1485
+ if (!uiDir || !files || files.length === 0) return result;
1486
+ const subfolder = getSubfolderFromPaths(files);
1487
+ if (!subfolder) return result;
1488
+ const uiFiles = files.filter((file) => file.type === "registry:ui");
1489
+ const targetDir = path.join(uiDir, subfolder);
1490
+ for (const file of uiFiles) {
1491
+ const filename = path.basename(file.path);
1492
+ const flatPath = path.join(uiDir, filename);
1493
+ const targetPath = path.join(targetDir, filename);
1494
+ if (!await pathExists(flatPath)) continue;
1495
+ if (await pathExists(targetPath)) {
1496
+ if (overwriteExisting) {
1497
+ await fs$1.mkdir(targetDir, { recursive: true });
1498
+ await fs$1.unlink(targetPath);
1499
+ await fs$1.rename(flatPath, targetPath);
1500
+ result.totalMoved++;
1501
+ result.movedFiles.push(`${subfolder}/${filename}`);
1502
+ if (verbose) logger.info(`[bejamas-ui] Replaced ${subfolder}/${filename} with the reinstalled version`);
1503
+ continue;
1504
+ }
1505
+ try {
1506
+ await fs$1.unlink(flatPath);
1507
+ result.skippedFiles.push(`${subfolder}/${filename}`);
1508
+ if (verbose) logger.info(`[bejamas-ui] Removed flat duplicate: ${filename} (${subfolder}/${filename} exists)`);
1509
+ } catch {
1510
+ result.skippedFiles.push(`${subfolder}/${filename}`);
1511
+ }
1512
+ continue;
1513
+ }
1514
+ await fs$1.mkdir(targetDir, { recursive: true });
1515
+ await fs$1.rename(flatPath, targetPath);
1516
+ result.totalMoved++;
1517
+ result.movedFiles.push(`${subfolder}/${filename}`);
1518
+ if (verbose) logger.info(`[bejamas-ui] Moved ${filename} -> ${subfolder}/${filename}`);
1519
+ }
1520
+ return result;
1521
+ }
1522
+ async function reorganizeComponents(components, uiDir, registryUrl, verbose, style = "bejamas-juno", overwriteExisting = false) {
1523
+ const result = {
1524
+ totalMoved: 0,
1525
+ movedFiles: [],
1526
+ skippedFiles: []
1527
+ };
1528
+ if (!uiDir || components.length === 0) return result;
1529
+ for (const componentName of components) try {
1530
+ const registryItem = await fetchRegistryItem(componentName, registryUrl, style);
1531
+ if (!registryItem) {
1532
+ if (verbose) logger.info(`[bejamas-ui] Could not fetch registry for ${componentName}, skipping reorganization`);
1533
+ continue;
1534
+ }
1535
+ if (!shouldReorganizeRegistryUiFiles(registryItem.files, uiDir)) continue;
1536
+ const componentResult = await reorganizeRegistryUiFiles(registryItem.files, uiDir, verbose, overwriteExisting);
1537
+ result.totalMoved += componentResult.totalMoved;
1538
+ result.movedFiles.push(...componentResult.movedFiles);
1539
+ result.skippedFiles.push(...componentResult.skippedFiles);
1540
+ if (componentResult.totalMoved > 0 && verbose) {
1541
+ const subfolder = getSubfolderFromPaths(registryItem.files);
1542
+ logger.info(`[bejamas-ui] Reorganized ${componentName} into ${subfolder}/`);
1543
+ }
1544
+ } catch (err) {
1545
+ if (verbose) logger.warn(`[bejamas-ui] Failed to reorganize ${componentName}: ${err}`);
281
1546
  }
1547
+ return result;
282
1548
  }
283
1549
 
284
1550
  //#endregion
285
1551
  //#region src/utils/shadcn-cli.ts
286
- const require = createRequire(import.meta.url);
287
- const PINNED_SHADCN_VERSION = "3.8.5";
1552
+ const PINNED_SHADCN_VERSION = "4.1.1";
288
1553
  const PINNED_SHADCN_PACKAGE = `shadcn@${PINNED_SHADCN_VERSION}`;
289
- function resolveBundledShadcnEntrypoint() {
290
- try {
291
- return require.resolve("shadcn");
292
- } catch {
293
- return null;
294
- }
1554
+ const PINNED_SHADCN_EXEC_PREFIX = path.join(os$1.tmpdir(), "bejamas-shadcn", PINNED_SHADCN_VERSION);
1555
+ async function ensurePinnedShadcnExecPrefix() {
1556
+ await fs$1.mkdir(PINNED_SHADCN_EXEC_PREFIX, { recursive: true });
1557
+ return PINNED_SHADCN_EXEC_PREFIX;
295
1558
  }
296
- function buildPinnedShadcnInvocation(shadcnArgs, bundledEntrypoint = resolveBundledShadcnEntrypoint()) {
297
- if (bundledEntrypoint) return {
298
- cmd: process.execPath,
299
- args: [bundledEntrypoint, ...shadcnArgs],
300
- source: "bundled"
301
- };
1559
+ function getNpmExecutable() {
1560
+ return process.platform === "win32" ? "npm.cmd" : "npm";
1561
+ }
1562
+ function buildPinnedShadcnInvocation(shadcnArgs) {
302
1563
  return {
303
- cmd: "npx",
1564
+ cmd: getNpmExecutable(),
304
1565
  args: [
305
- "-y",
306
- PINNED_SHADCN_PACKAGE,
1566
+ "exec",
1567
+ "--yes",
1568
+ "--prefix",
1569
+ PINNED_SHADCN_EXEC_PREFIX,
1570
+ `--package=${PINNED_SHADCN_PACKAGE}`,
1571
+ "--",
1572
+ "shadcn",
307
1573
  ...shadcnArgs
308
1574
  ],
309
- source: "fallback"
1575
+ source: "isolated"
310
1576
  };
311
1577
  }
312
1578
 
1579
+ //#endregion
1580
+ //#region src/utils/shadcn-command.ts
1581
+ function extractPassthroughArgs(rawArgv, commandName) {
1582
+ const commandIndex = rawArgv.findIndex((arg) => arg === commandName);
1583
+ if (commandIndex === -1) return [];
1584
+ const rest = rawArgv.slice(commandIndex + 1);
1585
+ const doubleDashIndex = rest.indexOf("--");
1586
+ if (doubleDashIndex === -1) return [];
1587
+ return rest.slice(doubleDashIndex + 1);
1588
+ }
1589
+ async function runShadcnCommand({ cwd, args, env }) {
1590
+ await ensurePinnedShadcnExecPrefix();
1591
+ const invocation = buildPinnedShadcnInvocation(args);
1592
+ await execa(invocation.cmd, invocation.args, {
1593
+ cwd,
1594
+ env: {
1595
+ ...process.env,
1596
+ ...env
1597
+ },
1598
+ stdio: "inherit"
1599
+ });
1600
+ }
1601
+
313
1602
  //#endregion
314
1603
  //#region src/commands/init.ts
315
- const DEFAULT_REGISTRY_URL$1 = "https://ui.bejamas.com/r";
316
- const DEFAULT_COMPONENTS_BASE_COLOR = "neutral";
1604
+ function resolveDesignSystemConfig(options) {
1605
+ const rtlLanguage = options.rtl && options.lang ? options.lang : DEFAULT_DESIGN_SYSTEM_CONFIG.rtlLanguage;
1606
+ if (options.preset && isPresetCode(options.preset)) {
1607
+ const decoded = decodePreset(options.preset);
1608
+ if (decoded) return normalizeDesignSystemConfig(designSystemConfigSchema.parse({
1609
+ ...DEFAULT_DESIGN_SYSTEM_CONFIG,
1610
+ ...decoded,
1611
+ template: options.template ?? DEFAULT_DESIGN_SYSTEM_CONFIG.template,
1612
+ rtl: options.rtl ?? false,
1613
+ rtlLanguage
1614
+ }));
1615
+ }
1616
+ const baseColor = options.baseColor ?? DEFAULT_DESIGN_SYSTEM_CONFIG.baseColor;
1617
+ return normalizeDesignSystemConfig(designSystemConfigSchema.parse({
1618
+ ...DEFAULT_DESIGN_SYSTEM_CONFIG,
1619
+ template: options.template ?? DEFAULT_DESIGN_SYSTEM_CONFIG.template,
1620
+ rtl: options.rtl ?? false,
1621
+ rtlLanguage,
1622
+ baseColor,
1623
+ theme: baseColor === DEFAULT_DESIGN_SYSTEM_CONFIG.baseColor ? DEFAULT_DESIGN_SYSTEM_CONFIG.theme : baseColor
1624
+ }));
1625
+ }
1626
+ function buildInitUrl(config, themeRef, env = process.env) {
1627
+ const params = new URLSearchParams({
1628
+ preset: encodePreset(config),
1629
+ template: config.template
1630
+ });
1631
+ if (config.rtl) {
1632
+ params.set("rtl", "true");
1633
+ params.set("lang", config.rtlLanguage);
1634
+ }
1635
+ if (themeRef) params.set("themeRef", themeRef);
1636
+ return `${buildUiUrl("/init", env)}?${params.toString()}`;
1637
+ }
1638
+ function buildThemeVarsFromRegistryItem(item) {
1639
+ return item?.cssVars ?? null;
1640
+ }
1641
+ async function fetchInitThemeVars(initUrl) {
1642
+ const response = await fetch(initUrl);
1643
+ if (!response.ok) return null;
1644
+ return buildThemeVarsFromRegistryItem(await response.json());
1645
+ }
317
1646
  const initOptionsSchema = z.object({
318
1647
  cwd: z.string(),
319
1648
  components: z.array(z.string()).optional(),
320
1649
  yes: z.boolean(),
1650
+ reinstall: z.boolean().optional(),
321
1651
  defaults: z.boolean(),
322
1652
  force: z.boolean(),
323
1653
  silent: z.boolean(),
@@ -327,20 +1657,98 @@ const initOptionsSchema = z.object({
327
1657
  template: z.string().optional().refine((val) => {
328
1658
  if (val) return TEMPLATES[val];
329
1659
  return true;
330
- }, { message: "Invalid template. Please use 'next' or 'next-monorepo'." }),
331
- baseColor: z.string().optional(),
332
- baseStyle: z.boolean()
1660
+ }, { message: "Invalid template. Please use 'astro', 'astro-monorepo', or 'astro-with-component-docs-monorepo'." }),
1661
+ preset: z.string().optional(),
1662
+ baseColor: z.string().optional().refine((val) => {
1663
+ if (val) return BASE_COLORS.find((color) => color.name === val);
1664
+ return true;
1665
+ }, { message: `Invalid base color. Please use '${BASE_COLORS.map((color) => color.name).join("', '")}'` }),
1666
+ baseStyle: z.boolean(),
1667
+ rtl: z.boolean().default(false),
1668
+ lang: z.enum(RTL_LANGUAGE_VALUES).optional(),
1669
+ themeRef: z.string().optional()
333
1670
  });
334
- function buildShadcnInitArgs(baseColor = DEFAULT_COMPONENTS_BASE_COLOR) {
1671
+ function shouldReinstallExistingComponents(options) {
1672
+ return options.reinstall ?? Boolean(options.preset);
1673
+ }
1674
+ function ensureShadcnReinstallFlag(forwardedOptions, shouldReinstall) {
1675
+ if (!shouldReinstall || forwardedOptions.includes("--reinstall") || forwardedOptions.includes("--no-reinstall")) return forwardedOptions;
1676
+ const passthroughIndex = forwardedOptions.indexOf("--");
1677
+ if (passthroughIndex === -1) return [...forwardedOptions, "--reinstall"];
335
1678
  return [
336
- "init",
337
- "--base-color",
338
- baseColor
1679
+ ...forwardedOptions.slice(0, passthroughIndex),
1680
+ "--reinstall",
1681
+ ...forwardedOptions.slice(passthroughIndex)
339
1682
  ];
340
1683
  }
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) => {
1684
+ function extractOptionsForShadcnInit(rawArgv, cmd) {
1685
+ if (typeof cmd.getOptionValueSource === "function") {
1686
+ const opts = cmd.optsWithGlobals();
1687
+ const forwarded$1 = [];
1688
+ const getSource = (key) => cmd.getOptionValueSource(key);
1689
+ const addBoolean = (key, flag, negateFlag) => {
1690
+ if (getSource(key) !== "cli") return;
1691
+ const value = opts[key];
1692
+ if (typeof value !== "boolean") return;
1693
+ if (value) forwarded$1.push(flag);
1694
+ else if (negateFlag) forwarded$1.push(negateFlag);
1695
+ };
1696
+ addBoolean("yes", "--yes");
1697
+ addBoolean("force", "--force");
1698
+ addBoolean("silent", "--silent");
1699
+ addBoolean("reinstall", "--reinstall", "--no-reinstall");
1700
+ const initIndex$1 = rawArgv.findIndex((arg) => arg === "init");
1701
+ if (initIndex$1 !== -1) {
1702
+ const rest$1 = rawArgv.slice(initIndex$1 + 1);
1703
+ const doubleDashIndex = rest$1.indexOf("--");
1704
+ if (doubleDashIndex !== -1) forwarded$1.push(...rest$1.slice(doubleDashIndex));
1705
+ }
1706
+ return forwarded$1;
1707
+ }
1708
+ const initIndex = rawArgv.findIndex((arg) => arg === "init");
1709
+ if (initIndex === -1) return [];
1710
+ const rest = rawArgv.slice(initIndex + 1);
1711
+ const forwarded = [];
1712
+ const filteredFlags = new Set([
1713
+ "-v",
1714
+ "--verbose",
1715
+ "-t",
1716
+ "--template",
1717
+ "-b",
1718
+ "--base-color",
1719
+ "-p",
1720
+ "--preset",
1721
+ "--theme-ref",
1722
+ "-c",
1723
+ "--cwd",
1724
+ "-d",
1725
+ "--defaults",
1726
+ "--src-dir",
1727
+ "--no-src-dir",
1728
+ "--css-variables",
1729
+ "--no-css-variables",
1730
+ "--no-base-style",
1731
+ "--rtl",
1732
+ "--lang"
1733
+ ]);
1734
+ for (let index = 0; index < rest.length; index += 1) {
1735
+ const token = rest[index];
1736
+ if (token === "--") {
1737
+ forwarded.push("--", ...rest.slice(index + 1));
1738
+ break;
1739
+ }
1740
+ if (!token.startsWith("-")) continue;
1741
+ if (filteredFlags.has(token)) continue;
1742
+ forwarded.push(token);
1743
+ }
1744
+ return forwarded;
1745
+ }
1746
+ 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
1747
  try {
343
- await runInit(opts);
1748
+ await runInit({
1749
+ ...opts,
1750
+ forwardedOptions: extractOptionsForShadcnInit(process.argv.slice(2), cmd)
1751
+ });
344
1752
  } catch (error) {
345
1753
  logger.break();
346
1754
  handleError(error);
@@ -349,6 +1757,7 @@ const init = new Command().name("init").description("initialize your project and
349
1757
  }
350
1758
  });
351
1759
  async function runInit(options) {
1760
+ const designConfig = resolveDesignSystemConfig(options);
352
1761
  let newProjectTemplate;
353
1762
  if (!options.skipPreflight) {
354
1763
  if ((await preFlightInit(options)).errors[MISSING_DIR_OR_EMPTY_PROJECT]) {
@@ -360,30 +1769,86 @@ async function runInit(options) {
360
1769
  }
361
1770
  }
362
1771
  if (newProjectTemplate) {
363
- options.cwd = path.resolve(options.cwd, {
1772
+ const projectPath = {
364
1773
  "astro-monorepo": "apps/web",
365
1774
  "astro-with-component-docs-monorepo": "apps/web",
366
1775
  astro: ""
367
- }[newProjectTemplate]);
1776
+ };
1777
+ await applyDesignSystemToProject(options.cwd, {
1778
+ ...designConfig,
1779
+ template: newProjectTemplate
1780
+ }, { themeVars: options.themeRef ? await fetchInitThemeVars(buildInitUrl({
1781
+ ...designConfig,
1782
+ template: newProjectTemplate
1783
+ }, options.themeRef)) ?? void 0 : void 0 });
1784
+ options.cwd = path.resolve(options.cwd, projectPath[newProjectTemplate]);
368
1785
  logger.log(`${highlighter.success("Success!")} Project initialization completed.\nYou may now add components.`);
369
1786
  return await getConfig(options.cwd);
370
1787
  }
371
1788
  try {
372
1789
  const env = {
373
1790
  ...process.env,
374
- REGISTRY_URL: process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL$1
1791
+ REGISTRY_URL: resolveRegistryUrl()
375
1792
  };
376
- const invocation = buildPinnedShadcnInvocation(buildShadcnInitArgs(options.baseColor ?? DEFAULT_COMPONENTS_BASE_COLOR));
377
- await execa(invocation.cmd, invocation.args, {
378
- stdio: "inherit",
1793
+ const initUrl = buildInitUrl(designConfig, options.themeRef);
1794
+ const shouldReinstall = shouldReinstallExistingComponents(options);
1795
+ const reinstallComponents = shouldReinstall ? await getInstalledUiComponents(options.cwd) : [];
1796
+ const forwardedOptions = ensureShadcnReinstallFlag(options.forwardedOptions ?? [], shouldReinstall);
1797
+ await runShadcnCommand({
379
1798
  cwd: options.cwd,
1799
+ args: [
1800
+ "init",
1801
+ initUrl,
1802
+ ...reinstallComponents,
1803
+ ...forwardedOptions
1804
+ ],
380
1805
  env
381
1806
  });
382
- } catch (err) {
1807
+ await syncManagedTailwindCss(options.cwd);
1808
+ const managedFonts = [toManagedAstroFont(designConfig.font), designConfig.fontHeading !== "inherit" ? toManagedAstroFont(`font-heading-${designConfig.fontHeading}`) : null].filter((font) => font !== null);
1809
+ const managedFont = managedFonts.find((font) => font.cssVariable !== "--font-heading");
1810
+ if (managedFonts.length > 0 && managedFont) {
1811
+ await syncAstroFontsInProject(options.cwd, managedFonts, managedFont.cssVariable);
1812
+ await syncAstroManagedFontCss(options.cwd, managedFont.cssVariable);
1813
+ await cleanupAstroFontPackages(options.cwd);
1814
+ }
1815
+ if (reinstallComponents.length > 0) {
1816
+ const config = await getConfig(options.cwd);
1817
+ const uiDir = config?.resolvedPaths.ui ?? "";
1818
+ const activeStyle = config?.style ?? "bejamas-juno";
1819
+ if (uiDir) await reorganizeComponents(reinstallComponents, uiDir, resolveRegistryUrl(), false, activeStyle, true);
1820
+ await fixAstroImports(options.cwd, false);
1821
+ }
1822
+ } catch {
383
1823
  process.exit(1);
384
1824
  }
385
1825
  }
386
1826
 
1827
+ //#endregion
1828
+ //#region src/commands/docs.ts
1829
+ 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) => {
1830
+ const cwd = path.resolve(opts.cwd ?? process.cwd());
1831
+ const rawArgv = process.argv.slice(2);
1832
+ const args = [
1833
+ "docs",
1834
+ ...components,
1835
+ "--cwd",
1836
+ cwd
1837
+ ];
1838
+ if (opts.base) args.push("--base", opts.base);
1839
+ if (opts.json) args.push("--json");
1840
+ const passthroughArgs = extractPassthroughArgs(rawArgv, "docs");
1841
+ if (passthroughArgs.length > 0) args.push(...passthroughArgs);
1842
+ try {
1843
+ await runShadcnCommand({
1844
+ cwd,
1845
+ args
1846
+ });
1847
+ } catch {
1848
+ process.exit(1);
1849
+ }
1850
+ });
1851
+
387
1852
  //#endregion
388
1853
  //#region src/utils/tsconfig-utils.ts
389
1854
  /**
@@ -421,7 +1886,7 @@ function resolveAliasPathUsingTsConfig(inputPath, projectRoot) {
421
1886
  }
422
1887
 
423
1888
  //#endregion
424
- //#region src/commands/docs.ts
1889
+ //#region src/commands/docs-build.ts
425
1890
  async function generateDocs({ cwd, outDir, verbose }) {
426
1891
  const DEBUG = process.env.BEJAMAS_DEBUG === "1" || process.env.BEJAMAS_DEBUG === "true" || verbose;
427
1892
  try {
@@ -460,7 +1925,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
460
1925
  if (mapped.startsWith("./") || mapped.startsWith("../") || isAbsolute(mapped)) outResolved = mapped;
461
1926
  else {
462
1927
  const abs = resolveAliasPathUsingTsConfig(mapped, projectRoot);
463
- if (abs) outResolved = relative$1(projectRoot, abs);
1928
+ if (abs) outResolved = relative(projectRoot, abs);
464
1929
  }
465
1930
  if (!outResolved && mapped.startsWith("@/")) outResolved = mapped.replace(/^@\//, "src/");
466
1931
  const finalOut = outResolved ?? mapped;
@@ -486,7 +1951,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
486
1951
  for (let i = 0; i < 6; i += 1) {
487
1952
  if (existsSync(resolve(current, "packages/ui/package.json"))) {
488
1953
  const abs = resolve(current, "packages/ui");
489
- return relative$1(shellCwd, abs) || abs;
1954
+ return relative(shellCwd, abs) || abs;
490
1955
  }
491
1956
  const parent = resolve(current, "..");
492
1957
  if (parent === current) break;
@@ -494,7 +1959,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
494
1959
  }
495
1960
  if (existsSync(resolve(shellCwd, "node_modules/@bejamas/ui/package.json"))) {
496
1961
  const abs = resolve(shellCwd, "node_modules/@bejamas/ui");
497
- return relative$1(shellCwd, abs) || abs;
1962
+ return relative(shellCwd, abs) || abs;
498
1963
  }
499
1964
  return "packages/ui";
500
1965
  })(),
@@ -533,7 +1998,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
533
1998
  if (process.env.BEJAMAS_DOCS_CWD) logger.info(`Docs CWD: ${process.env.BEJAMAS_DOCS_CWD}`);
534
1999
  if (process.env.BEJAMAS_DOCS_OUT_DIR) logger.info(`Docs out: ${process.env.BEJAMAS_DOCS_OUT_DIR}`);
535
2000
  }
536
- const mod = await import("./generate-mdx-BejBMCLk.js");
2001
+ const mod = await import("./generate-mdx-PPNpe9_J.js");
537
2002
  if (typeof mod.runDocsGenerator === "function") await mod.runDocsGenerator();
538
2003
  else throw new Error("Failed to load docs generator. Export 'runDocsGenerator' not found.");
539
2004
  } catch (err) {
@@ -541,7 +2006,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
541
2006
  process.exit(1);
542
2007
  }
543
2008
  }
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) => {
2009
+ 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
2010
  await generateDocs({
546
2011
  cwd: opts.cwd,
547
2012
  outDir: opts.out,
@@ -639,19 +2104,19 @@ async function checkDocs({ cwd, json }) {
639
2104
  logger.error("Unable to locate @bejamas/ui. Use --cwd to specify the UI package path.");
640
2105
  process.exit(1);
641
2106
  }
642
- const componentsDir = join$1(uiRoot, "src", "components");
2107
+ const componentsDir = join(uiRoot, "src", "components");
643
2108
  if (!existsSync(componentsDir)) {
644
2109
  logger.error(`Components directory not found: ${componentsDir}\n\nExpected structure: <uiRoot>/src/components/*.astro\nUse --cwd to specify a different UI package root.`);
645
2110
  process.exit(1);
646
2111
  }
647
- const files = readdirSync(componentsDir, { withFileTypes: true }).filter((e) => e.isFile() && extname$1(e.name).toLowerCase() === ".astro").map((e) => e.name).sort();
2112
+ const files = readdirSync(componentsDir, { withFileTypes: true }).filter((e) => e.isFile() && extname(e.name).toLowerCase() === ".astro").map((e) => e.name).sort();
648
2113
  if (files.length === 0) {
649
2114
  logger.warn("No .astro component files found.");
650
2115
  process.exit(0);
651
2116
  }
652
2117
  const results = [];
653
2118
  for (const file of files) {
654
- const status = checkComponentDocs(join$1(componentsDir, file), file);
2119
+ const status = checkComponentDocs(join(componentsDir, file), file);
655
2120
  results.push(status);
656
2121
  }
657
2122
  const complete = results.filter((r) => r.status === "complete");
@@ -718,174 +2183,8 @@ const docsCheck = new Command().name("docs:check").description("check documentat
718
2183
  });
719
2184
  });
720
2185
 
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
2186
  //#endregion
887
2187
  //#region src/commands/add.ts
888
- const DEFAULT_REGISTRY_URL = "https://ui.bejamas.com/r";
889
2188
  function extractOptionsForShadcn(rawArgv, cmd) {
890
2189
  if (typeof cmd.getOptionValueSource === "function") {
891
2190
  const opts = cmd.optsWithGlobals();
@@ -903,13 +2202,25 @@ function extractOptionsForShadcn(rawArgv, cmd) {
903
2202
  const value = opts[key];
904
2203
  if (typeof value === "string") forwarded$1.push(flag, value);
905
2204
  };
2205
+ const addOptionalString = (key, flag) => {
2206
+ if (getSource(key) !== "cli") return;
2207
+ const value = opts[key];
2208
+ if (value === true) {
2209
+ forwarded$1.push(flag);
2210
+ return;
2211
+ }
2212
+ if (typeof value === "string") forwarded$1.push(flag, value);
2213
+ };
906
2214
  addBoolean("yes", "--yes");
907
2215
  addBoolean("overwrite", "--overwrite");
2216
+ addBoolean("dryRun", "--dry-run");
908
2217
  addString("cwd", "--cwd");
909
2218
  addBoolean("all", "--all");
910
2219
  addString("path", "--path");
911
2220
  addBoolean("silent", "--silent");
912
2221
  addBoolean("srcDir", "--src-dir", "--no-src-dir");
2222
+ addOptionalString("diff", "--diff");
2223
+ addOptionalString("view", "--view");
913
2224
  const addIndex$1 = rawArgv.findIndex((arg) => arg === "add");
914
2225
  if (addIndex$1 !== -1) {
915
2226
  const rest$1 = rawArgv.slice(addIndex$1 + 1);
@@ -926,13 +2237,15 @@ function extractOptionsForShadcn(rawArgv, cmd) {
926
2237
  "-c",
927
2238
  "--cwd",
928
2239
  "-p",
929
- "--path"
2240
+ "--path",
2241
+ "--diff",
2242
+ "--view"
930
2243
  ]);
931
2244
  const filteredFlags = new Set(["-v", "--verbose"]);
932
- for (let i = 0; i < rest.length; i += 1) {
933
- const token = rest[i];
2245
+ for (let index = 0; index < rest.length; index += 1) {
2246
+ const token = rest[index];
934
2247
  if (token === "--") {
935
- forwarded.push("--", ...rest.slice(i + 1));
2248
+ forwarded.push("--", ...rest.slice(index + 1));
936
2249
  break;
937
2250
  }
938
2251
  if (!token.startsWith("-")) continue;
@@ -940,36 +2253,38 @@ function extractOptionsForShadcn(rawArgv, cmd) {
940
2253
  forwarded.push(token);
941
2254
  if (token.includes("=")) continue;
942
2255
  if (optionsWithValues.has(token)) {
943
- const next = rest[i + 1];
2256
+ const next = rest[index + 1];
944
2257
  if (next) {
945
2258
  forwarded.push(next);
946
- i += 1;
2259
+ index += 1;
947
2260
  }
948
2261
  }
949
2262
  }
950
2263
  return forwarded;
951
2264
  }
952
- /** Build maps for path rewriting, handling filename collisions */
953
- async function buildSubfolderMap(components, registryUrl) {
2265
+ function hasInspectionFlags(forwardedOptions) {
2266
+ return forwardedOptions.includes("--dry-run") || forwardedOptions.includes("--diff") || forwardedOptions.includes("--view");
2267
+ }
2268
+ function formatSkippedFilesHeading(count, overwriteUsed) {
2269
+ const noun = `file${count === 1 ? "" : "s"}`;
2270
+ if (overwriteUsed) return `Skipped ${count} ${noun}: (files might be identical)`;
2271
+ return `Skipped ${count} ${noun}: (files might be identical, use --overwrite to overwrite)`;
2272
+ }
2273
+ async function buildSubfolderMap(components, uiDir, registryUrl, style) {
954
2274
  const filenameToSubfolders = /* @__PURE__ */ new Map();
955
- const componentInfo = /* @__PURE__ */ new Map();
2275
+ let requiresReorganization = false;
956
2276
  for (const componentName of components) {
957
- const registryItem = await fetchRegistryItem(componentName, registryUrl);
2277
+ const registryItem = await fetchRegistryItem(componentName, registryUrl, style);
958
2278
  if (!registryItem) continue;
2279
+ if (shouldReorganizeRegistryUiFiles(registryItem.files, uiDir)) requiresReorganization = true;
959
2280
  const subfolder = getSubfolderFromPaths(registryItem.files);
960
2281
  if (!subfolder) continue;
961
- const files = [];
962
2282
  for (const file of registryItem.files) if (file.type === "registry:ui") {
963
- const filename = path$1.basename(file.path);
964
- files.push(filename);
2283
+ const filename = path.basename(file.path);
965
2284
  const subfolders = filenameToSubfolders.get(filename) || [];
966
2285
  subfolders.push(subfolder);
967
2286
  filenameToSubfolders.set(filename, subfolders);
968
2287
  }
969
- componentInfo.set(subfolder, {
970
- subfolder,
971
- files
972
- });
973
2288
  }
974
2289
  const uniqueMap = /* @__PURE__ */ new Map();
975
2290
  const sharedFilenames = /* @__PURE__ */ new Set();
@@ -979,35 +2294,29 @@ async function buildSubfolderMap(components, registryUrl) {
979
2294
  });
980
2295
  return {
981
2296
  uniqueMap,
982
- componentInfo,
983
- sharedFilenames
2297
+ sharedFilenames,
2298
+ requiresReorganization
984
2299
  };
985
2300
  }
986
- /**
987
- * Rewrite file paths to include correct subfolders.
988
- * Handles shared filenames (like index.ts) by tracking current component context.
989
- */
990
2301
  function rewritePaths(paths, mapResult) {
991
- const { uniqueMap, componentInfo, sharedFilenames } = mapResult;
2302
+ const { uniqueMap, sharedFilenames } = mapResult;
992
2303
  let currentSubfolder = null;
993
2304
  return paths.map((filePath) => {
994
- const filename = path$1.basename(filePath);
995
- const parentDir = path$1.basename(path$1.dirname(filePath));
2305
+ const filename = path.basename(filePath);
2306
+ const parentDir = path.basename(path.dirname(filePath));
996
2307
  const uniqueMapping = uniqueMap.get(filename);
997
2308
  if (uniqueMapping) {
998
- const expectedSubfolder = path$1.dirname(uniqueMapping);
2309
+ const expectedSubfolder = path.dirname(uniqueMapping);
999
2310
  currentSubfolder = expectedSubfolder;
1000
- if (parentDir !== expectedSubfolder) return `${path$1.dirname(filePath)}/${uniqueMapping}`;
2311
+ if (parentDir !== expectedSubfolder) return `${path.dirname(filePath)}/${uniqueMapping}`;
1001
2312
  return filePath;
1002
2313
  }
1003
2314
  if (sharedFilenames.has(filename) && currentSubfolder) {
1004
- const expectedSubfolder = currentSubfolder;
1005
- if (parentDir !== expectedSubfolder) return `${path$1.dirname(filePath)}/${expectedSubfolder}/${filename}`;
2315
+ if (parentDir !== currentSubfolder) return `${path.dirname(filePath)}/${currentSubfolder}/${filename}`;
1006
2316
  }
1007
2317
  return filePath;
1008
2318
  });
1009
2319
  }
1010
- /** Fetch available components from the registry */
1011
2320
  async function fetchAvailableComponents(registryUrl) {
1012
2321
  const indexUrl = `${registryUrl}/index.json`;
1013
2322
  const response = await fetch(indexUrl);
@@ -1015,14 +2324,13 @@ async function fetchAvailableComponents(registryUrl) {
1015
2324
  const data = await response.json();
1016
2325
  return Array.isArray(data) ? data : [];
1017
2326
  }
1018
- /** Prompt user to select components interactively */
1019
2327
  async function promptForComponents(registryUrl) {
1020
2328
  const checkingSpinner = spinner("Checking registry.").start();
1021
2329
  let components;
1022
2330
  try {
1023
2331
  components = await fetchAvailableComponents(registryUrl);
1024
2332
  checkingSpinner.succeed();
1025
- } catch (error) {
2333
+ } catch {
1026
2334
  checkingSpinner.fail();
1027
2335
  logger.error("Failed to fetch available components from registry.");
1028
2336
  return null;
@@ -1035,9 +2343,9 @@ async function promptForComponents(registryUrl) {
1035
2343
  type: "autocompleteMultiselect",
1036
2344
  name: "selected",
1037
2345
  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
2346
+ choices: components.filter((component) => !component.type || component.type === "registry:ui").map((component) => ({
2347
+ title: component.name,
2348
+ value: component.name
1041
2349
  })),
1042
2350
  hint: "- Space to select. Return to submit.",
1043
2351
  instructions: false
@@ -1045,7 +2353,6 @@ async function promptForComponents(registryUrl) {
1045
2353
  if (!selected) return null;
1046
2354
  return selected;
1047
2355
  }
1048
- /** Parse shadcn output to extract file lists (stdout has paths, stderr has headers) */
1049
2356
  function parseShadcnOutput(stdout, stderr) {
1050
2357
  const result = {
1051
2358
  created: [],
@@ -1072,23 +2379,34 @@ function parseShadcnOutput(stdout, stderr) {
1072
2379
  if (!allPaths.includes(filePath)) allPaths.push(filePath);
1073
2380
  }
1074
2381
  }
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++]);
2382
+ let index = 0;
2383
+ for (let count = 0; count < createdCount && index < allPaths.length; count += 1) {
2384
+ result.created.push(allPaths[index]);
2385
+ index += 1;
2386
+ }
2387
+ for (let count = 0; count < updatedCount && index < allPaths.length; count += 1) {
2388
+ result.updated.push(allPaths[index]);
2389
+ index += 1;
2390
+ }
2391
+ for (let count = 0; count < skippedCount && index < allPaths.length; count += 1) {
2392
+ result.skipped.push(allPaths[index]);
2393
+ index += 1;
2394
+ }
1079
2395
  return result;
1080
2396
  }
1081
- async function addComponents(packages, forwardedOptions, isVerbose, isSilent, subfolderMapResult) {
2397
+ async function addComponents(cwd, packages, forwardedOptions, isVerbose, isSilent, inspectionMode) {
1082
2398
  const env = {
1083
2399
  ...process.env,
1084
- REGISTRY_URL: process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL
2400
+ REGISTRY_URL: resolveRegistryUrl()
1085
2401
  };
2402
+ await ensurePinnedShadcnExecPrefix();
1086
2403
  const invocation = buildPinnedShadcnInvocation(buildShadcnAddArgs(packages, forwardedOptions));
1087
2404
  if (isVerbose) logger.info(`[bejamas-ui] ${invocation.cmd} ${invocation.args.join(" ")}`);
1088
2405
  const registrySpinner = spinner("Checking registry.", { silent: isSilent });
1089
2406
  registrySpinner.start();
1090
2407
  try {
1091
2408
  const result = await execa(invocation.cmd, invocation.args, {
2409
+ cwd,
1092
2410
  env,
1093
2411
  input: "n\nn\nn\nn\nn\nn\nn\nn\nn\nn\n",
1094
2412
  stdout: "pipe",
@@ -1103,13 +2421,21 @@ async function addComponents(packages, forwardedOptions, isVerbose, isSilent, su
1103
2421
  logger.info(`[bejamas-ui] Raw stdout: ${stdout}`);
1104
2422
  logger.info(`[bejamas-ui] Raw stderr: ${stderr}`);
1105
2423
  }
1106
- const parsed = parseShadcnOutput(stdout, stderr);
2424
+ if (inspectionMode) {
2425
+ if (stdout) process.stdout.write(stdout.endsWith("\n") ? stdout : `${stdout}\n`);
2426
+ if (stderr) process.stderr.write(stderr.endsWith("\n") ? stderr : `${stderr}\n`);
2427
+ }
2428
+ const parsed = inspectionMode ? {
2429
+ created: [],
2430
+ updated: [],
2431
+ skipped: []
2432
+ } : parseShadcnOutput(stdout, stderr);
1107
2433
  if (result.exitCode !== 0) {
1108
2434
  if (result.stderr) logger.error(result.stderr);
1109
2435
  process.exit(result.exitCode);
1110
2436
  }
1111
2437
  return parsed;
1112
- } catch (err) {
2438
+ } catch {
1113
2439
  registrySpinner.fail();
1114
2440
  logger.error("Failed to add components");
1115
2441
  process.exit(1);
@@ -1125,22 +2451,24 @@ function buildShadcnAddArgs(packages, forwardedOptions) {
1125
2451
  ...forwardedOptions
1126
2452
  ];
1127
2453
  }
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) {
2454
+ 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
2455
  const root = cmd?.parent;
1130
2456
  const verbose = Boolean(root?.opts?.().verbose);
1131
2457
  const forwardedOptions = extractOptionsForShadcn(process.argv.slice(2), cmd);
1132
2458
  const opts = typeof cmd.optsWithGlobals === "function" ? cmd.optsWithGlobals() : cmd.opts?.() ?? {};
2459
+ const inspectionMode = hasInspectionFlags(forwardedOptions);
2460
+ const overwriteUsed = forwardedOptions.includes("--overwrite") || forwardedOptions.includes("-o");
1133
2461
  const cwd = opts.cwd || process.cwd();
1134
2462
  let componentsToAdd = packages || [];
1135
2463
  const wantsAll = Boolean(opts.all);
1136
2464
  const isSilent = opts.silent || false;
1137
- const registryUrl = process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL;
2465
+ const registryUrl = resolveRegistryUrl();
1138
2466
  if (wantsAll && componentsToAdd.length === 0) {
1139
2467
  const fetchingSpinner = spinner("Fetching available components.", { silent: isSilent }).start();
1140
2468
  try {
1141
- componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter((c) => !c.type || c.type === "registry:ui").map((c) => c.name);
2469
+ componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter((component) => !component.type || component.type === "registry:ui").map((component) => component.name);
1142
2470
  fetchingSpinner.succeed();
1143
- } catch (error) {
2471
+ } catch {
1144
2472
  fetchingSpinner.fail();
1145
2473
  logger.error("Failed to fetch available components from registry.");
1146
2474
  process.exit(1);
@@ -1166,19 +2494,35 @@ const add = new Command().name("add").description("Add components via the pinned
1166
2494
  logger.info(`[bejamas-ui] uiDir: ${uiDir}`);
1167
2495
  logger.info(`[bejamas-ui] aliases.ui: ${uiConfig?.aliases?.ui || "not set"}`);
1168
2496
  }
2497
+ const activeStyle = uiConfig?.style || config?.style || "bejamas-juno";
1169
2498
  const totalComponents = componentsToAdd.length;
1170
- for (let i = 0; i < componentsToAdd.length; i++) {
1171
- const component = componentsToAdd[i];
2499
+ for (let index = 0; index < componentsToAdd.length; index += 1) {
2500
+ const component = componentsToAdd[index];
1172
2501
  if (totalComponents > 1 && !isSilent) {
1173
2502
  logger.break();
1174
- logger.info(highlighter.info(`[${i + 1}/${totalComponents}]`) + ` Adding ${highlighter.success(component)}...`);
2503
+ logger.info(highlighter.info(`[${index + 1}/${totalComponents}]`) + ` Adding ${highlighter.success(component)}...`);
2504
+ }
2505
+ const subfolderMapResult = inspectionMode ? {
2506
+ uniqueMap: /* @__PURE__ */ new Map(),
2507
+ sharedFilenames: /* @__PURE__ */ new Set(),
2508
+ requiresReorganization: false
2509
+ } : await buildSubfolderMap([component], uiDir, registryUrl, activeStyle);
2510
+ const parsed = await addComponents(cwd, [component], forwardedOptions, verbose, isSilent, inspectionMode);
2511
+ if (!inspectionMode) {
2512
+ await syncManagedTailwindCss(cwd);
2513
+ const registryItem = await fetchRegistryItem(component, registryUrl, activeStyle);
2514
+ if (registryItem?.type === "registry:font") {
2515
+ const nextFont = toManagedAstroFont(registryItem.name);
2516
+ if (nextFont) {
2517
+ await syncAstroFontsInProject(cwd, mergeManagedAstroFonts(await readManagedAstroFontsFromProject(cwd), nextFont), nextFont.cssVariable);
2518
+ await syncAstroManagedFontCss(cwd, nextFont.cssVariable);
2519
+ await cleanupAstroFontPackages(cwd);
2520
+ }
2521
+ }
1175
2522
  }
1176
- const subfolderMapResult = await buildSubfolderMap([component], registryUrl);
1177
- const parsed = await addComponents([component], forwardedOptions, verbose, isSilent, subfolderMapResult);
1178
2523
  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);
2524
+ if (!inspectionMode && uiDir && subfolderMapResult.requiresReorganization) skippedCount = (await reorganizeComponents([component], uiDir, registryUrl, verbose, activeStyle)).skippedFiles.length;
2525
+ if (!isSilent && !inspectionMode) {
1182
2526
  const actuallyCreated = Math.max(0, parsed.created.length - skippedCount);
1183
2527
  if (actuallyCreated > 0) {
1184
2528
  const createdPaths = rewritePaths(parsed.created.slice(0, actuallyCreated), subfolderMapResult);
@@ -1193,13 +2537,36 @@ const add = new Command().name("add").description("Add components via the pinned
1193
2537
  if (skippedCount > 0) logger.info(`Skipped ${skippedCount} file${skippedCount > 1 ? "s" : ""}: (already exists)`);
1194
2538
  if (parsed.skipped.length > 0) {
1195
2539
  const skippedPaths = rewritePaths(parsed.skipped, subfolderMapResult);
1196
- logger.info(`Skipped ${skippedPaths.length} file${skippedPaths.length > 1 ? "s" : ""}: (use --overwrite)`);
2540
+ logger.info(formatSkippedFilesHeading(skippedPaths.length, overwriteUsed));
1197
2541
  for (const file of skippedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
1198
2542
  }
1199
2543
  if (actuallyCreated === 0 && parsed.updated.length === 0 && skippedCount === 0 && parsed.skipped.length === 0) logger.info("Already up to date.");
1200
2544
  }
1201
2545
  }
1202
- await fixAstroImports(cwd, verbose);
2546
+ if (!inspectionMode) await fixAstroImports(cwd, verbose);
2547
+ });
2548
+
2549
+ //#endregion
2550
+ //#region src/commands/info.ts
2551
+ 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) => {
2552
+ const cwd = path.resolve(opts.cwd ?? process.cwd());
2553
+ const rawArgv = process.argv.slice(2);
2554
+ const args = [
2555
+ "info",
2556
+ "--cwd",
2557
+ cwd
2558
+ ];
2559
+ if (opts.json) args.push("--json");
2560
+ const passthroughArgs = extractPassthroughArgs(rawArgv, "info");
2561
+ if (passthroughArgs.length > 0) args.push(...passthroughArgs);
2562
+ try {
2563
+ await runShadcnCommand({
2564
+ cwd,
2565
+ args
2566
+ });
2567
+ } catch {
2568
+ process.exit(1);
2569
+ }
1203
2570
  });
1204
2571
 
1205
2572
  //#endregion
@@ -1208,7 +2575,9 @@ const pkg = createRequire(import.meta.url)("../package.json");
1208
2575
  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
2576
  program.addCommand(init);
1210
2577
  program.addCommand(add);
2578
+ program.addCommand(info);
1211
2579
  program.addCommand(docs);
2580
+ program.addCommand(docsBuild);
1212
2581
  program.addCommand(docsCheck);
1213
2582
  program.parse(process.argv);
1214
2583
  var src_default = program;