bejamas 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as getSemanticIconNameFromLucidePath, B as getStyleId, C as buildUiUrl, D as ICON_LIBRARY_COLLECTIONS, E as buildRegistryTheme, F as getDocumentDirection, G as fonts, H as decodePreset, I as getDocumentLanguage, L as getFontPackageName, M as DEFAULT_DESIGN_SYSTEM_CONFIG, N as RTL_LANGUAGE_VALUES, O as SEMANTIC_ICON_NAMES, P as designSystemConfigSchema, R as getFontValue, S as BASE_COLORS, T as ICON_LIBRARIES, U as encodePreset, V as normalizeDesignSystemConfig, W as isPresetCode, _ as getConfig, b as highlighter, d as parseJsDocMetadata, g as spinner, j as renderSemanticIconSvgWithAttributeString, k as getSemanticIconNameFromLucideExport, o as extractFrontmatter, p as resolveUiRoot, v as getWorkspaceConfig, w as resolveRegistryUrl, x as BEJAMAS_COMPONENTS_SCHEMA_URL, y as logger, z as getHeadingFontValue } from "./utils-B0HWwl6F.js";
2
+ import { $ as PRESET_RADII, A as getSemanticIconNameFromLucideExport, B as getHeadingFontValue, C as BASE_COLORS, D as buildRegistryTheme, E as ICON_LIBRARIES, F as designSystemConfigSchema, G as DEFAULT_PRESET_CONFIG, H as getStyleDefaultRadius, I as getDocumentDirection, J as PRESET_FONTS, K as PRESET_BASE_COLORS, L as getDocumentLanguage, M as renderSemanticIconSvgWithAttributeString, N as DEFAULT_DESIGN_SYSTEM_CONFIG, O as ICON_LIBRARY_COLLECTIONS, P as RTL_LANGUAGE_VALUES, Q as PRESET_MENU_COLORS, R as getFontPackageName, S as BEJAMAS_COMPONENTS_SCHEMA_URL, T as resolveRegistryUrl, U as getStyleId, V as getRadiusValue, W as normalizeDesignSystemConfig, X as PRESET_ICON_LIBRARIES, Y as PRESET_FONT_HEADINGS, Z as PRESET_MENU_ACCENTS, _ as getConfig, at as isPresetCode, b as logger, d as parseJsDocMetadata, et as PRESET_STYLES, g as spinner, it as encodePreset, j as getSemanticIconNameFromLucidePath, k as SEMANTIC_ICON_NAMES, nt as V1_CHART_COLOR_MAP, o as extractFrontmatter, ot as fonts, p as resolveUiRoot, q as PRESET_CHART_COLORS, rt as decodePreset, tt as PRESET_THEMES, v as getWorkspaceConfig, w as buildUiUrl, x as highlighter, y as getProjectInfo, z as getFontValue } from "./utils-otwzm7kH.js";
3
3
  import { Command } from "commander";
4
4
  import { createRequire } from "module";
5
5
  import path, { extname, isAbsolute, join, relative, resolve } from "node:path";
@@ -28,6 +28,7 @@ function clearRegistryContext() {
28
28
  //#endregion
29
29
  //#region src/utils/errors.ts
30
30
  const MISSING_DIR_OR_EMPTY_PROJECT = "1";
31
+ const MISSING_CONFIG = "3";
31
32
 
32
33
  //#endregion
33
34
  //#region src/preflights/preflight-init.ts
@@ -66,11 +67,11 @@ const MANAGED_BODY_FONT_CLASSES = new Set(["font-mono", "font-serif"]);
66
67
  function compactSource$1(source) {
67
68
  return source.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
68
69
  }
69
- function escapeRegExp$1(value) {
70
+ function escapeRegExp$2(value) {
70
71
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
71
72
  }
72
73
  function upsertImport(source, moduleName, importedName, fallbackNames = []) {
73
- const importPattern = /* @__PURE__ */ new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']${escapeRegExp$1(moduleName)}["'];?`);
74
+ const importPattern = /* @__PURE__ */ new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']${escapeRegExp$2(moduleName)}["'];?`);
74
75
  if (importPattern.test(source)) return source.replace(importPattern, (_match, importsSource) => {
75
76
  const imports = importsSource.split(",").map((value) => value.trim()).filter(Boolean);
76
77
  if (!imports.includes(importedName)) imports.push(importedName);
@@ -108,7 +109,7 @@ function upsertManagedBodyFontClass(source, cssVariable) {
108
109
  });
109
110
  }
110
111
  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 markerPattern = /* @__PURE__ */ new RegExp(`${escapeRegExp$2(ASTRO_CONFIG_BLOCK_START)}[\\s\\S]*?const\\s+${ASTRO_FONT_CONSTANT}\\s*=\\s*\\[([\\s\\S]*?)\\];[\\s\\S]*?${escapeRegExp$2(ASTRO_CONFIG_BLOCK_END)}`);
112
113
  const match = source.match(markerPattern);
113
114
  if (!match) return [];
114
115
  return (match[1].match(/\{[\s\S]*?\}/g) ?? []).map((entry) => {
@@ -137,7 +138,7 @@ function normalizeManagedAstroFontSubsets(subsets) {
137
138
  return [subsets[0], ...subsets.slice(1)];
138
139
  }
139
140
  function replaceManagedBlock(source, nextBlock, start, end) {
140
- const blockPattern = new RegExp(`${escapeRegExp$1(start)}[\\s\\S]*?${escapeRegExp$1(end)}`, "m");
141
+ const blockPattern = new RegExp(`${escapeRegExp$2(start)}[\\s\\S]*?${escapeRegExp$2(end)}`, "m");
141
142
  if (blockPattern.test(source)) return source.replace(blockPattern, nextBlock);
142
143
  return null;
143
144
  }
@@ -571,7 +572,7 @@ const [welcomePrefix, welcomeSuffix = ""] =
571
572
  `
572
573
  }
573
574
  ];
574
- function escapeRegExp(value) {
575
+ function escapeRegExp$1(value) {
575
576
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
576
577
  }
577
578
  function compactCss(source) {
@@ -581,8 +582,8 @@ function compactSource(source) {
581
582
  return source.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
582
583
  }
583
584
  function stripLegacyCreateBlock(source) {
584
- const fullBlockPattern = new RegExp(`\\n*${escapeRegExp(CREATE_BLOCK_START)}[\\s\\S]*?${escapeRegExp(CREATE_BLOCK_END)}\\n*`, "m");
585
- const danglingBlockPattern = new RegExp(`\\n*${escapeRegExp(CREATE_BLOCK_START)}[\\s\\S]*$`, "m");
585
+ const fullBlockPattern = new RegExp(`\\n*${escapeRegExp$1(CREATE_BLOCK_START)}[\\s\\S]*?${escapeRegExp$1(CREATE_BLOCK_END)}\\n*`, "m");
586
+ const danglingBlockPattern = new RegExp(`\\n*${escapeRegExp$1(CREATE_BLOCK_START)}[\\s\\S]*$`, "m");
586
587
  return compactCss(source.replace(fullBlockPattern, "\n\n").replace(danglingBlockPattern, "\n\n"));
587
588
  }
588
589
  function buildCssVarBlock(selector, vars) {
@@ -593,7 +594,7 @@ function buildCssVarBlock(selector, vars) {
593
594
  ].join("\n");
594
595
  }
595
596
  function replaceTopLevelBlock(source, selector, nextBlock) {
596
- const pattern = new RegExp(`^${escapeRegExp(selector)}\\s*\\{[\\s\\S]*?^\\}`, "m");
597
+ const pattern = new RegExp(`^${escapeRegExp$1(selector)}\\s*\\{[\\s\\S]*?^\\}`, "m");
597
598
  if (pattern.test(source)) return source.replace(pattern, nextBlock);
598
599
  const layerIndex = source.indexOf("@layer base");
599
600
  if (layerIndex !== -1) return `${source.slice(0, layerIndex).trimEnd()}\n\n${nextBlock}\n\n${source.slice(layerIndex).trimStart()}`;
@@ -701,18 +702,27 @@ function upsertBaseLayerHtmlFont(source, fontClass) {
701
702
  });
702
703
  }
703
704
  function transformDesignSystemCss(source, config, themeVars) {
705
+ return transformDesignSystemFontCss(transformDesignSystemThemeCss(source, config, themeVars), config);
706
+ }
707
+ function transformDesignSystemThemeCss(source, config, themeVars) {
704
708
  const effectiveThemeVars = themeVars ?? buildRegistryTheme(config).cssVars;
705
709
  const rootVars = {
706
710
  ...Object.fromEntries(Object.entries(effectiveThemeVars.theme ?? {}).filter(([key]) => key !== "bejamas-font-family")),
707
711
  ...effectiveThemeVars.light ?? {}
708
712
  };
709
713
  const darkVars = { ...effectiveThemeVars.dark ?? {} };
710
- const font = getFontValue(config.font);
711
714
  const tailwindImport = resolveManagedTailwindImport(source);
712
715
  let next = stripLegacyCreateBlock(source);
713
716
  next = upsertImports(next, [tailwindImport]);
714
717
  next = replaceTopLevelBlock(next, ":root", buildCssVarBlock(":root", rootVars));
715
718
  next = replaceTopLevelBlock(next, ".dark", buildCssVarBlock(".dark", darkVars));
719
+ return compactCss(next);
720
+ }
721
+ function transformDesignSystemFontCss(source, config) {
722
+ const font = getFontValue(config.font);
723
+ const tailwindImport = resolveManagedTailwindImport(source);
724
+ let next = stripLegacyCreateBlock(source);
725
+ next = upsertImports(next, [tailwindImport]);
716
726
  next = upsertThemeInlineFont(next, font?.font.variable);
717
727
  if (font) next = upsertBaseLayerHtmlFont(next, fontClassFromCssVariable(font.font.variable));
718
728
  return compactCss(next);
@@ -737,6 +747,8 @@ async function patchComponentsJson(filepath, config) {
737
747
  style: getStyleId(config.style),
738
748
  iconLibrary: config.iconLibrary,
739
749
  rtl: config.rtl,
750
+ menuColor: config.menuColor,
751
+ menuAccent: config.menuAccent,
740
752
  tailwind: {
741
753
  ...current.tailwind ?? {},
742
754
  baseColor: config.baseColor,
@@ -745,10 +757,17 @@ async function patchComponentsJson(filepath, config) {
745
757
  };
746
758
  await fs.writeJson(filepath, next, { spaces: 2 });
747
759
  }
760
+ async function patchComponentsJsonWithTheme(filepath, config) {
761
+ await patchComponentsJson(filepath, config);
762
+ }
748
763
  async function patchCssFileWithTheme(filepath, config, themeVars) {
749
764
  const next = transformDesignSystemCss(await fs.readFile(filepath, "utf8"), config, themeVars);
750
765
  await fs.writeFile(filepath, next, "utf8");
751
766
  }
767
+ async function patchCssFileWithThemeOnly(filepath, config, themeVars) {
768
+ const next = transformDesignSystemThemeCss(await fs.readFile(filepath, "utf8"), config, themeVars);
769
+ await fs.writeFile(filepath, next, "utf8");
770
+ }
752
771
  async function patchCssFileWithAstroFont(filepath, fontVariable) {
753
772
  const next = transformAstroManagedFontCss(await fs.readFile(filepath, "utf8"), fontVariable);
754
773
  await fs.writeFile(filepath, next, "utf8");
@@ -886,10 +905,36 @@ async function applyDesignSystemToProject(projectPath, config, options = {}) {
886
905
  await Promise.all([path.resolve(projectPath, "src/i18n/ui.ts"), path.resolve(projectPath, "apps/web/src/i18n/ui.ts")].map((filepath) => patchTemplateI18nFile(filepath, config)));
887
906
  await Promise.all([path.resolve(projectPath, "src/layouts/Layout.astro"), path.resolve(projectPath, "apps/web/src/layouts/Layout.astro")].map((filepath) => patchLayoutFile(filepath, config)));
888
907
  await Promise.all([path.resolve(projectPath, "src/pages/index.astro"), path.resolve(projectPath, "apps/web/src/pages/index.astro")].map((filepath) => patchStarterPageFile(filepath, config)));
908
+ await syncDesignSystemFontsInProject(projectPath, config);
909
+ }
910
+ async function applyDesignSystemThemeToProject(projectPath, config, options = {}) {
911
+ const componentJsonFiles = await fg("**/components.json", {
912
+ cwd: projectPath,
913
+ ignore: [
914
+ "**/node_modules/**",
915
+ "**/dist/**",
916
+ "**/.astro/**"
917
+ ]
918
+ });
919
+ const cssFiles = /* @__PURE__ */ new Set();
920
+ for (const relativePath of componentJsonFiles) {
921
+ const absolutePath = path.resolve(projectPath, relativePath);
922
+ await patchComponentsJsonWithTheme(absolutePath, config);
923
+ const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
924
+ if (typeof cssPath === "string" && cssPath.length > 0) cssFiles.add(path.resolve(path.dirname(absolutePath), cssPath));
925
+ }
926
+ await Promise.all(Array.from(cssFiles).map((filepath) => patchCssFileWithThemeOnly(filepath, config, options.themeVars)));
927
+ await syncPackageIconDependencies(projectPath, config);
928
+ }
929
+ async function applyDesignSystemFontToProject(projectPath, config) {
930
+ await syncDesignSystemFontsInProject(projectPath, config);
931
+ }
932
+ async function syncDesignSystemFontsInProject(projectPath, config) {
889
933
  const managedFonts = [toManagedAstroFont(config.font), config.fontHeading !== "inherit" ? toManagedAstroFont(`font-heading-${config.fontHeading}`) : null].filter((font) => font !== null);
890
934
  const managedFont = managedFonts.find((font) => font.cssVariable !== "--font-heading");
891
935
  if (managedFonts.length > 0 && managedFont) {
892
936
  await syncAstroFontsInProject(projectPath, managedFonts, managedFont.cssVariable);
937
+ await syncAstroManagedFontCss(projectPath, managedFont.cssVariable);
893
938
  await cleanupAstroFontPackages(projectPath);
894
939
  }
895
940
  }
@@ -1454,9 +1499,9 @@ async function getInstalledUiComponents(cwd) {
1454
1499
  //#endregion
1455
1500
  //#region src/utils/reorganize-components.ts
1456
1501
  async function fetchRegistryItem(componentName, registryUrl, style = "bejamas-juno") {
1457
- const url = `${registryUrl}/styles/${style}/${componentName}.json`;
1502
+ const url$1 = `${registryUrl}/styles/${style}/${componentName}.json`;
1458
1503
  try {
1459
- const response = await fetch(url);
1504
+ const response = await fetch(url$1);
1460
1505
  if (!response.ok) {
1461
1506
  const fallbackUrl = `${registryUrl}/${componentName}.json`;
1462
1507
  const fallbackResponse = await fetch(fallbackUrl);
@@ -1592,7 +1637,7 @@ async function reorganizeComponents(components, uiDir, registryUrl, verbose, sty
1592
1637
 
1593
1638
  //#endregion
1594
1639
  //#region src/utils/shadcn-cli.ts
1595
- const PINNED_SHADCN_VERSION = "4.1.1";
1640
+ const PINNED_SHADCN_VERSION = "4.6.0";
1596
1641
  const PINNED_SHADCN_PACKAGE = `shadcn@${PINNED_SHADCN_VERSION}`;
1597
1642
  const PINNED_SHADCN_EXEC_PREFIX = path.join(os$1.tmpdir(), "bejamas-shadcn", PINNED_SHADCN_VERSION);
1598
1643
  async function ensurePinnedShadcnExecPrefix() {
@@ -1829,45 +1874,43 @@ async function runInit(options) {
1829
1874
  return await getConfig(options.cwd);
1830
1875
  }
1831
1876
  try {
1832
- const env = {
1833
- ...process.env,
1834
- REGISTRY_URL: resolveRegistryUrl()
1835
- };
1836
- const initUrl = buildInitUrl(designConfig, options.themeRef);
1837
- const themeVars = options.themeRef ? await fetchInitThemeVars(initUrl) ?? void 0 : void 0;
1838
- const shouldReinstall = shouldReinstallExistingComponents(options);
1839
- const reinstallComponents = shouldReinstall ? await getInstalledUiComponents(options.cwd) : [];
1840
- const forwardedOptions = ensureShadcnReinstallFlag(options.forwardedOptions ?? [], shouldReinstall);
1841
- await runShadcnCommand({
1842
- cwd: options.cwd,
1843
- args: [
1844
- "init",
1845
- initUrl,
1846
- ...reinstallComponents,
1847
- ...forwardedOptions
1848
- ],
1849
- env
1850
- });
1851
- await syncManagedTailwindCss(options.cwd);
1852
- const managedFonts = [toManagedAstroFont(designConfig.font), designConfig.fontHeading !== "inherit" ? toManagedAstroFont(`font-heading-${designConfig.fontHeading}`) : null].filter((font) => font !== null);
1853
- const managedFont = managedFonts.find((font) => font.cssVariable !== "--font-heading");
1854
- if (managedFonts.length > 0 && managedFont) {
1855
- await syncAstroFontsInProject(options.cwd, managedFonts, managedFont.cssVariable);
1856
- await syncAstroManagedFontCss(options.cwd, managedFont.cssVariable);
1857
- await cleanupAstroFontPackages(options.cwd);
1858
- }
1859
- await applyDesignSystemToProject(options.cwd, designConfig, { themeVars });
1860
- if (reinstallComponents.length > 0) {
1861
- const config = await getConfig(options.cwd);
1862
- const uiDir = config?.resolvedPaths.ui ?? "";
1863
- const activeStyle = config?.style ?? "bejamas-juno";
1864
- if (uiDir) await reorganizeComponents(reinstallComponents, uiDir, resolveRegistryUrl(), false, activeStyle, true);
1865
- await fixAstroImports(options.cwd, false);
1866
- }
1877
+ return await runExistingProjectInit(options, designConfig);
1867
1878
  } catch {
1868
1879
  process.exit(1);
1869
1880
  }
1870
1881
  }
1882
+ async function runExistingProjectInit(options, designConfig = resolveDesignSystemConfig(options)) {
1883
+ const env = {
1884
+ ...process.env,
1885
+ REGISTRY_URL: resolveRegistryUrl()
1886
+ };
1887
+ const initUrl = buildInitUrl(designConfig, options.themeRef);
1888
+ const themeVars = options.themeRef ? await fetchInitThemeVars(initUrl) ?? void 0 : void 0;
1889
+ const shouldReinstall = shouldReinstallExistingComponents(options);
1890
+ const reinstallComponents = shouldReinstall ? await getInstalledUiComponents(options.cwd) : [];
1891
+ const forwardedOptions = ensureShadcnReinstallFlag(options.forwardedOptions ?? [], shouldReinstall);
1892
+ await runShadcnCommand({
1893
+ cwd: options.cwd,
1894
+ args: [
1895
+ "init",
1896
+ initUrl,
1897
+ ...reinstallComponents,
1898
+ ...forwardedOptions
1899
+ ],
1900
+ env
1901
+ });
1902
+ await syncManagedTailwindCss(options.cwd);
1903
+ await syncDesignSystemFontsInProject(options.cwd, designConfig);
1904
+ await applyDesignSystemToProject(options.cwd, designConfig, { themeVars });
1905
+ if (reinstallComponents.length > 0) {
1906
+ const config = await getConfig(options.cwd);
1907
+ const uiDir = config?.resolvedPaths.ui ?? "";
1908
+ const activeStyle = config?.style ?? "bejamas-juno";
1909
+ if (uiDir) await reorganizeComponents(reinstallComponents, uiDir, resolveRegistryUrl(), false, activeStyle, true);
1910
+ await fixAstroImports(options.cwd, false);
1911
+ }
1912
+ return await getConfig(options.cwd);
1913
+ }
1871
1914
 
1872
1915
  //#endregion
1873
1916
  //#region src/commands/docs.ts
@@ -2043,7 +2086,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
2043
2086
  if (process.env.BEJAMAS_DOCS_CWD) logger.info(`Docs CWD: ${process.env.BEJAMAS_DOCS_CWD}`);
2044
2087
  if (process.env.BEJAMAS_DOCS_OUT_DIR) logger.info(`Docs out: ${process.env.BEJAMAS_DOCS_OUT_DIR}`);
2045
2088
  }
2046
- const mod = await import("./generate-mdx-_F9W0nYJ.js");
2089
+ const mod = await import("./generate-mdx-oWy6Hzgd.js");
2047
2090
  if (typeof mod.runDocsGenerator === "function") await mod.runDocsGenerator();
2048
2091
  else throw new Error("Failed to load docs generator. Export 'runDocsGenerator' not found.");
2049
2092
  } catch (err) {
@@ -2595,6 +2638,495 @@ const add = new Command().name("add").description("Add components via the Bejama
2595
2638
  if (!inspectionMode) await fixAstroImports(cwd, verbose);
2596
2639
  });
2597
2640
 
2641
+ //#endregion
2642
+ //#region src/utils/file-helper.ts
2643
+ const FILE_BACKUP_SUFFIX = ".bak";
2644
+ var FileBackupError = class extends Error {
2645
+ constructor(filePath) {
2646
+ super(`Could not back up ${filePath}.`);
2647
+ this.name = "FileBackupError";
2648
+ this.filePath = filePath;
2649
+ }
2650
+ };
2651
+ function createFileBackup(filePath) {
2652
+ if (!fs.existsSync(filePath)) return null;
2653
+ const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
2654
+ if (fs.existsSync(backupPath)) return null;
2655
+ try {
2656
+ fs.copyFileSync(filePath, backupPath);
2657
+ return backupPath;
2658
+ } catch {
2659
+ return null;
2660
+ }
2661
+ }
2662
+ function restoreFileBackup(filePath) {
2663
+ const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
2664
+ if (!fs.existsSync(backupPath)) return false;
2665
+ try {
2666
+ fs.copyFileSync(backupPath, filePath);
2667
+ fs.unlinkSync(backupPath);
2668
+ return true;
2669
+ } catch (error) {
2670
+ console.error(`Warning: Could not restore backup file ${backupPath}: ${error}`);
2671
+ return false;
2672
+ }
2673
+ }
2674
+ function deleteFileBackup(filePath) {
2675
+ const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
2676
+ if (!fs.existsSync(backupPath)) return false;
2677
+ try {
2678
+ fs.unlinkSync(backupPath);
2679
+ return true;
2680
+ } catch {
2681
+ return false;
2682
+ }
2683
+ }
2684
+
2685
+ //#endregion
2686
+ //#region src/utils/get-monorepo-info.ts
2687
+ const FRAMEWORK_CONFIG_FILES = [
2688
+ "next.config.*",
2689
+ "vite.config.*",
2690
+ "astro.config.*",
2691
+ "remix.config.*",
2692
+ "nuxt.config.*",
2693
+ "svelte.config.*",
2694
+ "gatsby-config.*",
2695
+ "angular.json"
2696
+ ];
2697
+ async function isMonorepoRoot(cwd) {
2698
+ if (fs.existsSync(path.resolve(cwd, "pnpm-workspace.yaml"))) return true;
2699
+ const packageJsonPath = path.resolve(cwd, "package.json");
2700
+ if (fs.existsSync(packageJsonPath)) try {
2701
+ if ((await fs.readJson(packageJsonPath)).workspaces) return true;
2702
+ } catch {
2703
+ return false;
2704
+ }
2705
+ if (fs.existsSync(path.resolve(cwd, "lerna.json"))) return true;
2706
+ if (fs.existsSync(path.resolve(cwd, "nx.json"))) return true;
2707
+ return false;
2708
+ }
2709
+ async function getMonorepoTargets(cwd) {
2710
+ const patterns = await getWorkspacePatterns(cwd);
2711
+ if (!patterns.length) return [];
2712
+ const dirs = await fg(patterns, {
2713
+ cwd,
2714
+ onlyDirectories: true,
2715
+ ignore: ["**/node_modules/**"]
2716
+ });
2717
+ const targets = [];
2718
+ for (const dir of dirs) {
2719
+ const fullPath = path.resolve(cwd, dir);
2720
+ if (!fs.existsSync(path.resolve(fullPath, "package.json"))) continue;
2721
+ const hasComponentsJson = fs.existsSync(path.resolve(fullPath, "components.json"));
2722
+ const hasFrameworkConfig = FRAMEWORK_CONFIG_FILES.some((pattern) => fg.sync(pattern, {
2723
+ cwd: fullPath,
2724
+ dot: true
2725
+ }).length > 0);
2726
+ if (hasComponentsJson || hasFrameworkConfig) targets.push({
2727
+ name: dir,
2728
+ hasConfig: hasComponentsJson
2729
+ });
2730
+ }
2731
+ return targets;
2732
+ }
2733
+ function formatMonorepoMessage(command, targets, options = {}) {
2734
+ const binary = options.binary ?? "bejamas";
2735
+ const cwdFlag = options.cwdFlag ?? "-c";
2736
+ logger.break();
2737
+ logger.log(`It looks like you are running ${highlighter.info(command)} from a monorepo root.`);
2738
+ logger.log(`To use ${binary} in a specific workspace, use the ${highlighter.info(cwdFlag)} flag:`);
2739
+ logger.break();
2740
+ for (const target of targets) logger.log(` ${binary} ${command} ${cwdFlag} ${target.name}`);
2741
+ logger.break();
2742
+ }
2743
+ async function getWorkspacePatterns(cwd) {
2744
+ const patterns = [];
2745
+ const pnpmWorkspacePath = path.resolve(cwd, "pnpm-workspace.yaml");
2746
+ if (fs.existsSync(pnpmWorkspacePath)) {
2747
+ const content = await fs.readFile(pnpmWorkspacePath, "utf8");
2748
+ const matches = Array.from(content.matchAll(/^\s*-\s*["']?([^"'\n#]+)["']?\s*$/gm));
2749
+ for (const match of matches) patterns.push(match[1].trim());
2750
+ }
2751
+ const packageJsonPath = path.resolve(cwd, "package.json");
2752
+ if (fs.existsSync(packageJsonPath)) try {
2753
+ const packageJson = await fs.readJson(packageJsonPath);
2754
+ const workspaces = Array.isArray(packageJson.workspaces) ? packageJson.workspaces : packageJson.workspaces?.packages;
2755
+ if (Array.isArray(workspaces)) patterns.push(...workspaces.filter((pattern) => !pattern.startsWith("!")));
2756
+ } catch {
2757
+ return Array.from(new Set(patterns));
2758
+ }
2759
+ return Array.from(new Set(patterns));
2760
+ }
2761
+
2762
+ //#endregion
2763
+ //#region src/commands/apply.ts
2764
+ const applyOptionsSchema = z.object({
2765
+ cwd: z.string(),
2766
+ positionalPreset: z.string().optional(),
2767
+ preset: z.string().optional(),
2768
+ only: z.union([z.boolean(), z.string()]).optional(),
2769
+ yes: z.boolean(),
2770
+ silent: z.boolean()
2771
+ });
2772
+ const APPLY_ONLY_VALUES = ["theme", "font"];
2773
+ var ApplyOnlyError = class extends Error {
2774
+ constructor(message) {
2775
+ super(message);
2776
+ this.name = "ApplyOnlyError";
2777
+ }
2778
+ };
2779
+ var ApplyPresetError = class extends Error {
2780
+ constructor(message) {
2781
+ super(message);
2782
+ this.name = "ApplyPresetError";
2783
+ }
2784
+ };
2785
+ var ApplyWorkspaceSyncError = class extends Error {
2786
+ constructor(message) {
2787
+ super(message);
2788
+ this.name = "ApplyWorkspaceSyncError";
2789
+ }
2790
+ };
2791
+ const apply = new Command().name("apply").description("apply a preset to an existing project").argument("[preset]", "the preset to apply").option("--preset <preset>", "preset configuration to apply").option("--only [parts]", "apply only parts of a preset: theme, font").option("-y, --yes", "skip confirmation prompt.", false).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-s, --silent", "mute output.", false).action(async (positionalPreset, opts) => {
2792
+ try {
2793
+ const options = applyOptionsSchema.parse({
2794
+ ...opts,
2795
+ cwd: path.resolve(opts.cwd),
2796
+ positionalPreset
2797
+ });
2798
+ const rawPreset = resolveApplyPreset(options);
2799
+ const explicitOnly = resolveApplyOnly(options.only);
2800
+ validateApplyOnlyPreset({
2801
+ preset: rawPreset,
2802
+ only: explicitOnly
2803
+ });
2804
+ const preflight = await preFlightApply(options);
2805
+ if (preflight.errors[MISSING_DIR_OR_EMPTY_PROJECT]) {
2806
+ logger.break();
2807
+ logger.error(`The ${highlighter.info("apply")} command only works in an existing project.`);
2808
+ logger.error(`Run ${highlighter.info(getInitCommand(rawPreset))} first.`);
2809
+ logger.break();
2810
+ process.exit(1);
2811
+ }
2812
+ if (preflight.errors[MISSING_CONFIG]) {
2813
+ logger.break();
2814
+ logger.error(`No ${highlighter.info("components.json")} found at ${highlighter.info(options.cwd)}.`);
2815
+ logger.error(`Run ${highlighter.info(getInitCommand(rawPreset))} first.`);
2816
+ logger.break();
2817
+ process.exit(1);
2818
+ }
2819
+ const existingConfig = preflight.config;
2820
+ if (!existingConfig) process.exit(1);
2821
+ if (!rawPreset) {
2822
+ await printPresetBuilderGuidance(options);
2823
+ process.exit(0);
2824
+ }
2825
+ const resolvedPreset = resolveApplyPresetSource(rawPreset);
2826
+ const only = explicitOnly ?? resolveApplyOnly(resolvedPreset.only ?? getPresetUrlOnly(rawPreset));
2827
+ const designConfig = resolveApplyDesignSystemConfig(resolvedPreset, existingConfig);
2828
+ await confirmApply({
2829
+ cwd: options.cwd,
2830
+ only,
2831
+ silent: options.silent,
2832
+ yes: options.yes
2833
+ });
2834
+ await withFileBackups(await getApplyConfigPaths(existingConfig), async () => {
2835
+ if (only) await runPartialApply(options.cwd, designConfig, resolvedPreset, only);
2836
+ else await runExistingProjectInit({
2837
+ cwd: options.cwd,
2838
+ components: void 0,
2839
+ yes: true,
2840
+ reinstall: true,
2841
+ defaults: false,
2842
+ force: false,
2843
+ silent: options.silent,
2844
+ isNewProject: false,
2845
+ srcDir: false,
2846
+ cssVariables: true,
2847
+ template: "astro",
2848
+ preset: resolvedPreset.preset,
2849
+ baseColor: void 0,
2850
+ baseStyle: true,
2851
+ rtl: existingConfig.rtl ?? false,
2852
+ lang: void 0,
2853
+ themeRef: resolvedPreset.themeRef,
2854
+ forwardedOptions: ["--yes", "--force"]
2855
+ }, designConfig);
2856
+ const config = await getConfig(options.cwd);
2857
+ if (!config) throw new Error("Could not read components.json after applying preset.");
2858
+ await syncApplyWorkspaceConfigs(config, { only });
2859
+ });
2860
+ if (!options.silent) {
2861
+ logger.break();
2862
+ logger.log("Preset applied successfully.");
2863
+ logger.break();
2864
+ }
2865
+ } catch (error) {
2866
+ if (error instanceof ApplyOnlyError || error instanceof ApplyPresetError) {
2867
+ for (const line of error.message.split("\n")) logger.error(line);
2868
+ logger.break();
2869
+ process.exit(1);
2870
+ }
2871
+ if (error instanceof FileBackupError) {
2872
+ logger.error(`Could not back up ${highlighter.info("components.json")}. Aborting.`);
2873
+ logger.break();
2874
+ process.exit(1);
2875
+ }
2876
+ if (error instanceof ApplyWorkspaceSyncError) {
2877
+ logger.error(error.message);
2878
+ logger.break();
2879
+ process.exit(1);
2880
+ }
2881
+ logger.break();
2882
+ handleError(error);
2883
+ } finally {
2884
+ clearRegistryContext();
2885
+ }
2886
+ });
2887
+ function resolveApplyPreset(options) {
2888
+ const positionalPreset = options.positionalPreset?.trim();
2889
+ const flagPreset = options.preset?.trim();
2890
+ if (positionalPreset && flagPreset && positionalPreset !== flagPreset) throw new ApplyPresetError(`Received two different preset values. Use either the positional preset or ${highlighter.info("--preset")}, or pass the same value to both.`);
2891
+ return flagPreset ?? positionalPreset;
2892
+ }
2893
+ function getPresetUrlOnly(preset$1) {
2894
+ if (!isUrl(preset$1)) return;
2895
+ return new URL(preset$1).searchParams.get("only") ?? void 0;
2896
+ }
2897
+ function resolveApplyOnly(value) {
2898
+ if (value === void 0 || value === false) return;
2899
+ if (value === true) throw new ApplyOnlyError([
2900
+ "Missing value for --only.",
2901
+ `Use one or more of: ${APPLY_ONLY_VALUES.join(", ")}.`,
2902
+ "Example: bejamas apply <preset> --only theme,font."
2903
+ ].join("\n"));
2904
+ return parseApplyOnlyParts(value);
2905
+ }
2906
+ function parseApplyOnlyParts(value) {
2907
+ const aliases = {
2908
+ theme: "theme",
2909
+ font: "font",
2910
+ fonts: "font"
2911
+ };
2912
+ const parts = value.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
2913
+ const invalid = parts.filter((part) => !aliases[part]);
2914
+ if (!parts.length || invalid.length) throw new ApplyOnlyError([
2915
+ `Invalid value for --only: ${value}.`,
2916
+ `Use one or more of: ${APPLY_ONLY_VALUES.join(", ")}.`,
2917
+ "Example: bejamas apply <preset> --only theme,font."
2918
+ ].join("\n"));
2919
+ return Array.from(new Set(parts.map((part) => aliases[part])));
2920
+ }
2921
+ function validateApplyOnlyPreset(options) {
2922
+ if (!options.only || options.preset) return;
2923
+ throw new ApplyOnlyError(["Missing preset for --only.", "Use: bejamas apply <preset> --only theme,font."].join("\n"));
2924
+ }
2925
+ function resolveApplyPresetSource(preset$1) {
2926
+ if (isPresetCode(preset$1)) {
2927
+ validatePresetCode(preset$1);
2928
+ return { preset: preset$1 };
2929
+ }
2930
+ if (!isUrl(preset$1)) throw new ApplyPresetError(`Invalid preset: ${highlighter.info(preset$1)}. Use a preset code or a Bejamas create/init URL containing a preset code.`);
2931
+ const url$1 = new URL(preset$1);
2932
+ if (!isSupportedPresetUrl(url$1)) throw new ApplyPresetError(`Invalid preset URL: ${highlighter.info(preset$1)}. Use a Bejamas create or init URL containing a preset code.`);
2933
+ const code = url$1.searchParams.get("preset")?.trim();
2934
+ if (!code) throw new ApplyPresetError(`Invalid preset URL: ${highlighter.info(preset$1)}. Expected a URL containing a preset query parameter.`);
2935
+ validatePresetCode(code);
2936
+ return {
2937
+ preset: code,
2938
+ themeRef: url$1.searchParams.get("themeRef") ?? void 0,
2939
+ only: url$1.searchParams.get("only") ?? void 0
2940
+ };
2941
+ }
2942
+ async function preFlightApply(options) {
2943
+ const errors = {};
2944
+ if (!fs.existsSync(options.cwd) || !fs.existsSync(path.resolve(options.cwd, "package.json"))) {
2945
+ errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
2946
+ return {
2947
+ errors,
2948
+ config: null
2949
+ };
2950
+ }
2951
+ if (!fs.existsSync(path.resolve(options.cwd, "components.json"))) {
2952
+ if (await isMonorepoRoot(options.cwd)) {
2953
+ const targets = await getMonorepoTargets(options.cwd);
2954
+ const applyTargets = [];
2955
+ for (const target of targets) {
2956
+ const projectInfo = await getProjectInfo(path.resolve(options.cwd, target.name));
2957
+ if (target.hasConfig || projectInfo?.isAstro) applyTargets.push(target);
2958
+ }
2959
+ if (applyTargets.length > 0) {
2960
+ formatMonorepoMessage("apply --preset <preset>", applyTargets, {
2961
+ binary: "bejamas",
2962
+ cwdFlag: "-c"
2963
+ });
2964
+ process.exit(1);
2965
+ }
2966
+ }
2967
+ errors[MISSING_CONFIG] = true;
2968
+ return {
2969
+ errors,
2970
+ config: null
2971
+ };
2972
+ }
2973
+ try {
2974
+ return {
2975
+ errors,
2976
+ config: await getConfig(options.cwd)
2977
+ };
2978
+ } catch {
2979
+ logger.break();
2980
+ logger.error(`An invalid ${highlighter.info("components.json")} file was found at ${highlighter.info(options.cwd)}.\nBefore you can apply a preset, you must create a valid ${highlighter.info("components.json")} file by running the ${highlighter.info("init")} command.`);
2981
+ logger.break();
2982
+ process.exit(1);
2983
+ }
2984
+ }
2985
+ async function printPresetBuilderGuidance(options) {
2986
+ if (options.silent) return;
2987
+ const createUrl = buildUiUrl("/create");
2988
+ logger.break();
2989
+ logger.log(`Build your custom preset on ${highlighter.info(createUrl)}.`);
2990
+ logger.log(`Then run ${highlighter.info("bejamas apply --preset <preset>")} with the preset code or preset URL from ui.bejamas.com.`);
2991
+ logger.break();
2992
+ }
2993
+ async function confirmApply(options) {
2994
+ if (options.yes) return;
2995
+ logger.break();
2996
+ if (!options.only) logger.warn(highlighter.warn("Applying a new preset will overwrite existing UI components, fonts, and CSS variables."));
2997
+ else logger.warn(highlighter.warn("Applying the selected preset parts will update your project configuration and styles."));
2998
+ logger.warn("Commit or stash your changes before continuing so you can easily go back.");
2999
+ if (!options.only) {
3000
+ const reinstallComponents = await getInstalledUiComponents(options.cwd);
3001
+ logger.break();
3002
+ logger.log(" The following components will be re-installed:");
3003
+ if (reinstallComponents.length) for (let index = 0; index < reinstallComponents.length; index += 8) logger.log(` - ${reinstallComponents.slice(index, index + 8).join(", ")}`);
3004
+ else logger.log(" - No installed UI components were detected.");
3005
+ }
3006
+ logger.break();
3007
+ const { proceed } = await prompts({
3008
+ type: "confirm",
3009
+ name: "proceed",
3010
+ message: "Would you like to continue?",
3011
+ initial: false
3012
+ });
3013
+ if (!proceed) {
3014
+ logger.break();
3015
+ process.exit(1);
3016
+ }
3017
+ }
3018
+ async function runPartialApply(cwd, designConfig, preset$1, only) {
3019
+ if (only.includes("theme")) {
3020
+ const initUrl = buildInitUrl(designConfig, preset$1.themeRef);
3021
+ await applyDesignSystemThemeToProject(cwd, designConfig, { themeVars: preset$1.themeRef ? await fetchInitThemeVars(initUrl) ?? void 0 : void 0 });
3022
+ await fixAstroImports(cwd, false);
3023
+ }
3024
+ if (only.includes("font")) await applyDesignSystemFontToProject(cwd, designConfig);
3025
+ }
3026
+ function resolveApplyDesignSystemConfig(preset$1, existingConfig) {
3027
+ return resolveDesignSystemConfig({
3028
+ preset: preset$1.preset,
3029
+ template: "astro",
3030
+ rtl: existingConfig.rtl ?? false,
3031
+ baseColor: void 0,
3032
+ lang: void 0
3033
+ });
3034
+ }
3035
+ function validatePresetCode(preset$1) {
3036
+ if (!isPresetCode(preset$1) || !decodePreset(preset$1)) throw new ApplyPresetError(`Invalid preset code: ${highlighter.info(preset$1)}.`);
3037
+ }
3038
+ async function getApplyConfigPaths(config) {
3039
+ const configPaths = new Set([path.resolve(config.resolvedPaths.cwd, "components.json")]);
3040
+ const workspaceConfig = await getWorkspaceConfig(config);
3041
+ if (workspaceConfig) for (const linkedConfig of Object.values(workspaceConfig)) configPaths.add(path.resolve(linkedConfig.resolvedPaths.cwd, "components.json"));
3042
+ const existingPaths = [];
3043
+ for (const configPath of configPaths) if (await fs.pathExists(configPath)) existingPaths.push(configPath);
3044
+ return existingPaths.sort();
3045
+ }
3046
+ async function withFileBackups(filePaths, task) {
3047
+ const backedUpPaths = [];
3048
+ const restoreBackupOnExit = () => {
3049
+ for (const filePath of [...backedUpPaths].reverse()) restoreFileBackup(filePath);
3050
+ };
3051
+ try {
3052
+ for (const filePath of filePaths) {
3053
+ if (!createFileBackup(filePath)) throw new FileBackupError(filePath);
3054
+ backedUpPaths.push(filePath);
3055
+ }
3056
+ process.on("exit", restoreBackupOnExit);
3057
+ const result = await task();
3058
+ process.removeListener("exit", restoreBackupOnExit);
3059
+ for (const filePath of backedUpPaths) deleteFileBackup(filePath);
3060
+ return result;
3061
+ } catch (error) {
3062
+ process.removeListener("exit", restoreBackupOnExit);
3063
+ for (const filePath of [...backedUpPaths].reverse()) restoreFileBackup(filePath);
3064
+ throw error;
3065
+ }
3066
+ }
3067
+ async function syncApplyWorkspaceConfigs(config, options) {
3068
+ if (options.only && !options.only.includes("theme")) return;
3069
+ const linkedConfigs = await getApplyWorkspaceConfigs(config);
3070
+ if (!linkedConfigs.length) return;
3071
+ const patch = {
3072
+ style: config.style,
3073
+ tailwind: {
3074
+ baseColor: config.tailwind.baseColor,
3075
+ cssVariables: config.tailwind.cssVariables
3076
+ },
3077
+ ...config.iconLibrary ? { iconLibrary: config.iconLibrary } : {},
3078
+ ...config.rtl !== void 0 ? { rtl: config.rtl } : {},
3079
+ ...config.menuColor ? { menuColor: config.menuColor } : {},
3080
+ ...config.menuAccent ? { menuAccent: config.menuAccent } : {}
3081
+ };
3082
+ try {
3083
+ for (const linkedConfig of linkedConfigs) {
3084
+ const configPath = path.resolve(linkedConfig.resolvedPaths.cwd, "components.json");
3085
+ if (!await fs.pathExists(configPath)) continue;
3086
+ const existingConfig = await fs.readJson(configPath);
3087
+ await fs.writeJson(configPath, {
3088
+ ...existingConfig,
3089
+ ...patch,
3090
+ tailwind: {
3091
+ ...existingConfig.tailwind,
3092
+ ...patch.tailwind
3093
+ }
3094
+ }, { spaces: 2 });
3095
+ }
3096
+ } catch (error) {
3097
+ throw new ApplyWorkspaceSyncError(`Failed to sync linked workspace configs.${error instanceof Error ? ` ${error.message}` : ""}`);
3098
+ }
3099
+ }
3100
+ async function getApplyWorkspaceConfigs(config) {
3101
+ const workspaceConfig = await getWorkspaceConfig(config);
3102
+ if (!workspaceConfig) return [];
3103
+ const linkedConfigs = /* @__PURE__ */ new Map();
3104
+ for (const linkedConfig of Object.values(workspaceConfig)) {
3105
+ if (linkedConfig.resolvedPaths.cwd === config.resolvedPaths.cwd) continue;
3106
+ linkedConfigs.set(linkedConfig.resolvedPaths.cwd, linkedConfig);
3107
+ }
3108
+ return Array.from(linkedConfigs.values()).sort((a, b) => a.resolvedPaths.cwd.localeCompare(b.resolvedPaths.cwd));
3109
+ }
3110
+ function quoteShellArg(value) {
3111
+ return /[^A-Za-z0-9_./:-]/.test(value) ? JSON.stringify(value) : value;
3112
+ }
3113
+ function getInitCommand(preset$1) {
3114
+ if (!preset$1) return "bejamas init";
3115
+ return `bejamas init --preset ${quoteShellArg(preset$1)}`;
3116
+ }
3117
+ function isUrl(value) {
3118
+ try {
3119
+ new URL(value);
3120
+ return true;
3121
+ } catch {
3122
+ return false;
3123
+ }
3124
+ }
3125
+ function isSupportedPresetUrl(url$1) {
3126
+ const pathname = url$1.pathname.replace(/\/+$/, "");
3127
+ return pathname === "/create" || pathname === "/init";
3128
+ }
3129
+
2598
3130
  //#endregion
2599
3131
  //#region src/commands/info.ts
2600
3132
  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) => {
@@ -2618,12 +3150,451 @@ const info = new Command().name("info").description("proxy to shadcn info").opti
2618
3150
  }
2619
3151
  });
2620
3152
 
3153
+ //#endregion
3154
+ //#region src/utils/preset-resolve.ts
3155
+ const PRESET_BASE_COLOR_SET = new Set(PRESET_BASE_COLORS);
3156
+ const PRESET_FONT_SET = new Set(PRESET_FONTS);
3157
+ const PRESET_FONT_HEADING_SET = new Set(PRESET_FONT_HEADINGS);
3158
+ const PRESET_ICON_LIBRARY_SET = new Set(PRESET_ICON_LIBRARIES);
3159
+ const PRESET_MENU_ACCENT_SET = new Set(PRESET_MENU_ACCENTS);
3160
+ const PRESET_MENU_COLOR_SET = new Set(PRESET_MENU_COLORS);
3161
+ const PRESET_STYLE_SET = new Set(PRESET_STYLES);
3162
+ const ROOT_FONT_VARIABLES = [
3163
+ "--font-sans",
3164
+ "--font-serif",
3165
+ "--font-mono"
3166
+ ];
3167
+ const ROOT_FONT_VARIABLE_SET = new Set(ROOT_FONT_VARIABLES);
3168
+ const FONT_VARIABLE_SET = new Set([...ROOT_FONT_VARIABLES, "--font-heading"]);
3169
+ async function resolveProjectPreset(config) {
3170
+ const style = normalizePresetStyle(config.style);
3171
+ if (!style) return {
3172
+ code: null,
3173
+ fallbacks: [],
3174
+ values: null
3175
+ };
3176
+ const cssState = await readCssState(config.resolvedPaths.tailwindCss);
3177
+ const baseColor = asPresetValue(PRESET_BASE_COLOR_SET, config.tailwind.baseColor) ?? DEFAULT_PRESET_CONFIG.baseColor;
3178
+ const iconLibrary = asPresetValue(PRESET_ICON_LIBRARY_SET, config.iconLibrary) ?? DEFAULT_PRESET_CONFIG.iconLibrary;
3179
+ const menuAccent = asPresetValue(PRESET_MENU_ACCENT_SET, config.menuAccent) ?? DEFAULT_PRESET_CONFIG.menuAccent;
3180
+ const menuColor = asPresetValue(PRESET_MENU_COLOR_SET, config.menuColor) ?? DEFAULT_PRESET_CONFIG.menuColor;
3181
+ const resolvedTheme = matchTheme(cssState, {
3182
+ baseColor,
3183
+ menuAccent,
3184
+ menuColor,
3185
+ style
3186
+ });
3187
+ const theme = resolvedTheme ?? getFallbackTheme(baseColor);
3188
+ const resolvedRadius = matchRadius(cssState.rootVars["--radius"], style);
3189
+ const radius = resolvedRadius ?? DEFAULT_PRESET_CONFIG.radius;
3190
+ const resolvedFont = await resolveBodyFont(config, cssState);
3191
+ const font = resolvedFont ?? DEFAULT_PRESET_CONFIG.font;
3192
+ const resolvedFontHeading = await resolveHeadingFont(config, cssState, font);
3193
+ const values = {
3194
+ style,
3195
+ baseColor,
3196
+ theme,
3197
+ iconLibrary,
3198
+ font,
3199
+ fontHeading: normalizeFontHeading(resolvedFontHeading ?? DEFAULT_PRESET_CONFIG.fontHeading, font),
3200
+ radius,
3201
+ menuAccent,
3202
+ menuColor
3203
+ };
3204
+ const fallbacks = [
3205
+ !asPresetValue(PRESET_BASE_COLOR_SET, config.tailwind.baseColor) && "baseColor",
3206
+ !resolvedTheme && "theme",
3207
+ !asPresetValue(PRESET_ICON_LIBRARY_SET, config.iconLibrary) && "iconLibrary",
3208
+ !resolvedFont && "font",
3209
+ !resolvedFontHeading && "fontHeading",
3210
+ !resolvedRadius && "radius",
3211
+ !asPresetValue(PRESET_MENU_ACCENT_SET, config.menuAccent) && "menuAccent",
3212
+ !asPresetValue(PRESET_MENU_COLOR_SET, config.menuColor) && "menuColor"
3213
+ ].filter(Boolean);
3214
+ return {
3215
+ code: encodePreset(values),
3216
+ fallbacks,
3217
+ values
3218
+ };
3219
+ }
3220
+ async function readCssState(tailwindCssPath) {
3221
+ const fallback = {
3222
+ darkVars: {},
3223
+ imports: [],
3224
+ rootVars: {},
3225
+ source: "",
3226
+ themeVars: {}
3227
+ };
3228
+ if (!tailwindCssPath || !await fs.pathExists(tailwindCssPath)) return fallback;
3229
+ try {
3230
+ const source = await fs.readFile(tailwindCssPath, "utf8");
3231
+ return {
3232
+ darkVars: collectBlockDeclarations(source, ".dark"),
3233
+ imports: collectImports(source),
3234
+ rootVars: collectBlockDeclarations(source, ":root"),
3235
+ source,
3236
+ themeVars: collectAtRuleDeclarations(source, "@theme inline")
3237
+ };
3238
+ } catch {
3239
+ return fallback;
3240
+ }
3241
+ }
3242
+ function collectImports(source) {
3243
+ const imports = [];
3244
+ const pattern = /@import\s+(?:url\((['"]?)(.+?)\1\)|(['"])(.+?)\3)\s*;/g;
3245
+ let match;
3246
+ while (match = pattern.exec(source)) {
3247
+ const value = match[2] ?? match[4];
3248
+ if (value) imports.push(value);
3249
+ }
3250
+ return imports;
3251
+ }
3252
+ function collectBlockDeclarations(source, selector) {
3253
+ const pattern = new RegExp(`${escapeRegExp(selector)}\\s*\\{([\\s\\S]*?)\\}`, "m");
3254
+ const match = source.match(pattern);
3255
+ return match ? collectDeclarations(match[1]) : {};
3256
+ }
3257
+ function collectAtRuleDeclarations(source, atRule) {
3258
+ const pattern = new RegExp(`${escapeRegExp(atRule)}\\s*\\{([\\s\\S]*?)\\}`, "m");
3259
+ const match = source.match(pattern);
3260
+ return match ? collectDeclarations(match[1]) : {};
3261
+ }
3262
+ function collectDeclarations(source) {
3263
+ const declarations = {};
3264
+ const pattern = /(--[a-z0-9-]+)\s*:\s*([^;]+);/gi;
3265
+ let match;
3266
+ while (match = pattern.exec(source)) declarations[match[1]] = match[2].trim();
3267
+ return declarations;
3268
+ }
3269
+ function matchTheme(state, options) {
3270
+ const lightPrimary = state.rootVars["--primary"];
3271
+ if (!lightPrimary) return null;
3272
+ for (const theme of PRESET_THEMES) {
3273
+ let registryTheme;
3274
+ try {
3275
+ registryTheme = buildRegistryTheme({
3276
+ ...DEFAULT_DESIGN_SYSTEM_FOR_MATCHING,
3277
+ ...options,
3278
+ theme
3279
+ }).cssVars;
3280
+ } catch {
3281
+ continue;
3282
+ }
3283
+ const expectedLight = registryTheme.light?.primary;
3284
+ const expectedDark = registryTheme.dark?.primary;
3285
+ if (!valuesMatch(lightPrimary, expectedLight)) continue;
3286
+ const darkPrimary = state.darkVars["--primary"];
3287
+ if (darkPrimary && expectedDark && !valuesMatch(darkPrimary, expectedDark)) continue;
3288
+ return theme;
3289
+ }
3290
+ return null;
3291
+ }
3292
+ function getFallbackTheme(baseColor) {
3293
+ return baseColor === DEFAULT_PRESET_CONFIG.baseColor ? DEFAULT_PRESET_CONFIG.theme : baseColor;
3294
+ }
3295
+ function matchRadius(value, style) {
3296
+ if (!value) return null;
3297
+ const normalizedValue = normalizeCssValue(value);
3298
+ const styleDefaultRadius = getStyleDefaultRadius(style);
3299
+ if (styleDefaultRadius && normalizedValue === normalizeCssValue(getRadiusValue(styleDefaultRadius))) return "default";
3300
+ for (const radius of PRESET_RADII) if (normalizedValue === normalizeCssValue(getRadiusValue(radius))) return radius === "default" ? "medium" : radius;
3301
+ return null;
3302
+ }
3303
+ async function resolveBodyFont(config, state) {
3304
+ const activeVariable = resolveActiveBodyFontVariable(state);
3305
+ const managedFonts = await readManagedAstroFontsFromProject(config.resolvedPaths.cwd);
3306
+ const matchedManagedFont = matchManagedFont(activeVariable ? managedFonts.find((font) => font.cssVariable === activeVariable) : managedFonts.find((font) => ROOT_FONT_VARIABLE_SET.has(font.cssVariable)), "body");
3307
+ if (matchedManagedFont) return matchedManagedFont;
3308
+ const matchedImport = activeVariable ? matchFontByImports(state.imports, activeVariable) : null;
3309
+ if (matchedImport) return matchedImport;
3310
+ for (const variable of ROOT_FONT_VARIABLES) {
3311
+ const matched = matchFontFromVariable(state, variable);
3312
+ if (matched) return matched;
3313
+ }
3314
+ return null;
3315
+ }
3316
+ async function resolveHeadingFont(config, state, bodyFont) {
3317
+ const managedHeadingFont = matchManagedFont((await readManagedAstroFontsFromProject(config.resolvedPaths.cwd)).find((font) => font.cssVariable === "--font-heading"), "heading");
3318
+ if (managedHeadingFont) return managedHeadingFont === bodyFont ? "inherit" : managedHeadingFont;
3319
+ const resolved = resolveFontValue(state, "--font-heading");
3320
+ const matched = resolved ? parseFontFromFamily(resolved) : null;
3321
+ if (matched) return matched === bodyFont ? "inherit" : matched;
3322
+ const value = getCssVariableValue(state, "--font-heading");
3323
+ const reference = value ? getVarReference(value) : null;
3324
+ if (reference && ROOT_FONT_VARIABLE_SET.has(reference)) {
3325
+ const matchedRootFont = matchFontFromVariable(state, reference);
3326
+ if (!matchedRootFont || matchedRootFont === bodyFont) return "inherit";
3327
+ return matchedRootFont;
3328
+ }
3329
+ return "inherit";
3330
+ }
3331
+ function normalizePresetStyle(style) {
3332
+ if (!style) return null;
3333
+ const normalized = style.replace(/^(bejamas|base|radix)-/, "");
3334
+ if (!PRESET_STYLE_SET.has(normalized)) return null;
3335
+ return normalized;
3336
+ }
3337
+ function normalizeFontHeading(fontHeading, bodyFont) {
3338
+ const normalized = fontHeading === bodyFont ? "inherit" : fontHeading;
3339
+ return PRESET_FONT_HEADING_SET.has(normalized) ? normalized : DEFAULT_PRESET_CONFIG.fontHeading;
3340
+ }
3341
+ function resolveActiveBodyFontVariable(state) {
3342
+ const htmlOrBodyFont = state.source.match(/(?:html|body)\s*\{[\s\S]*?@apply[^;]*\bfont-(sans|serif|mono)\b[\s\S]*?\}/m)?.[1];
3343
+ if (htmlOrBodyFont) return `--font-${htmlOrBodyFont}`;
3344
+ for (const variable of ROOT_FONT_VARIABLES) if (state.themeVars[variable]) return variable;
3345
+ return null;
3346
+ }
3347
+ function matchManagedFont(managedFont, slot) {
3348
+ if (!managedFont) return null;
3349
+ for (const font of PRESET_FONTS) {
3350
+ const expected = slot === "heading" ? toManagedAstroFont(`font-heading-${font}`) : toManagedAstroFont(font);
3351
+ if (expected && expected.cssVariable === managedFont.cssVariable && normalizeFontName(expected.name) === normalizeFontName(managedFont.name)) return font;
3352
+ }
3353
+ return null;
3354
+ }
3355
+ function matchFontByImports(imports, variable) {
3356
+ const matches = imports.flatMap((input) => {
3357
+ const font = parseFontFromDependency(input);
3358
+ const fontValue = font ? getFontValue(font) : null;
3359
+ return font && fontValue?.font.variable === variable ? [font] : [];
3360
+ });
3361
+ return matches.length === 1 ? matches[0] : null;
3362
+ }
3363
+ function matchFontFromVariable(state, variable) {
3364
+ const resolved = resolveFontValue(state, variable);
3365
+ return resolved ? parseFontFromFamily(resolved) : null;
3366
+ }
3367
+ function resolveFontValue(state, variable, seen = /* @__PURE__ */ new Set()) {
3368
+ if (seen.has(variable)) return null;
3369
+ seen.add(variable);
3370
+ const value = getCssVariableValue(state, variable);
3371
+ if (!value) return null;
3372
+ const reference = getVarReference(value);
3373
+ if (!reference) return value;
3374
+ if (FONT_VARIABLE_SET.has(reference)) return resolveFontValue(state, reference, seen);
3375
+ return null;
3376
+ }
3377
+ function getCssVariableValue(state, variable) {
3378
+ const themeValue = state.themeVars[variable];
3379
+ if (themeValue && getVarReference(themeValue) !== variable) return themeValue;
3380
+ return state.rootVars[variable] ?? themeValue ?? null;
3381
+ }
3382
+ function getVarReference(value) {
3383
+ return normalizeCssValue(value).match(/^var\((--[a-z0-9-]+)\)$/)?.[1] ?? null;
3384
+ }
3385
+ function parseFontFromDependency(value) {
3386
+ if (!value) return null;
3387
+ const prefix = "@fontsource-variable/";
3388
+ const normalized = normalizeCssValue(value);
3389
+ return normalized.startsWith(prefix) ? toPresetFont(normalized.slice(21)) : null;
3390
+ }
3391
+ function parseFontFromFamily(value) {
3392
+ if (!value) return null;
3393
+ const primaryFamily = normalizeFontName(value.split(",")[0] ?? "");
3394
+ for (const font of PRESET_FONTS) {
3395
+ const bodyFont = getFontValue(font);
3396
+ const headingFont = getHeadingFontValue(font);
3397
+ if ([
3398
+ font,
3399
+ bodyFont?.title,
3400
+ bodyFont?.font.family.split(",")[0],
3401
+ bodyFont?.font.import,
3402
+ headingFont?.title?.replace(/\s+\(Heading\)$/, ""),
3403
+ headingFont?.font.family.split(",")[0],
3404
+ headingFont?.font.import
3405
+ ].some((candidate) => candidate && normalizeFontName(candidate) === primaryFamily)) return font;
3406
+ }
3407
+ return null;
3408
+ }
3409
+ function toPresetFont(value) {
3410
+ const normalized = normalizeCssValue(value);
3411
+ return PRESET_FONT_SET.has(normalized) ? normalized : null;
3412
+ }
3413
+ function normalizeFontName(value) {
3414
+ return value.trim().replace(/^['"]|['"]$/g, "").replace(/\s+variable$/i, "").replace(/_/g, "-").replace(/\s+/g, "-").toLowerCase();
3415
+ }
3416
+ function normalizeCssValue(value) {
3417
+ if (!value) return "";
3418
+ return value.trim().replace(/-?\d*\.\d+/g, (match) => String(Number(match))).replace(/\s+/g, " ").replace(/\s*,\s*/g, ",").replace(/\s*\/\s*/g, "/").replace(/"/g, "'").toLowerCase();
3419
+ }
3420
+ function valuesMatch(actual, expected) {
3421
+ return normalizeCssValue(actual) === normalizeCssValue(expected);
3422
+ }
3423
+ function asPresetValue(set, value) {
3424
+ return value && set.has(value) ? value : null;
3425
+ }
3426
+ function escapeRegExp(value) {
3427
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3428
+ }
3429
+ const DEFAULT_DESIGN_SYSTEM_FOR_MATCHING = {
3430
+ ...DEFAULT_PRESET_CONFIG,
3431
+ template: "astro",
3432
+ rtl: false,
3433
+ rtlLanguage: "ar"
3434
+ };
3435
+
3436
+ //#endregion
3437
+ //#region src/commands/preset.ts
3438
+ const PRESET_CHART_COLOR_SET = new Set(PRESET_CHART_COLORS);
3439
+ function getPresetUrl(code) {
3440
+ return buildUiUrl(`/create?preset=${code}`);
3441
+ }
3442
+ function decodePresetCode(code) {
3443
+ const decoded = decodePreset(code);
3444
+ if (!decoded) throw new Error(`Invalid preset code: ${code}`);
3445
+ const derived = [];
3446
+ const chartColor = resolveDecodedChartColor(decoded);
3447
+ if (!decoded.chartColor && chartColor) derived.push("chartColor");
3448
+ return {
3449
+ code,
3450
+ derived,
3451
+ url: getPresetUrl(code),
3452
+ values: {
3453
+ ...decoded,
3454
+ ...chartColor ? { chartColor } : {}
3455
+ },
3456
+ version: code[0]
3457
+ };
3458
+ }
3459
+ function printPresetDecode(result) {
3460
+ printPresetInfo({
3461
+ code: result.code,
3462
+ fallbacks: result.derived,
3463
+ values: result.values
3464
+ }, { fallbackNote: " * Compatibility value for older preset versions." });
3465
+ }
3466
+ function printPresetInfo(preset$1, options = {}) {
3467
+ logger.log(highlighter.info("Preset"));
3468
+ if (!preset$1.code || !preset$1.values) {
3469
+ printEntries({ code: "-" });
3470
+ return;
3471
+ }
3472
+ const fallbacks = preset$1.fallbacks ?? [];
3473
+ const formatPresetValue = (key, value) => {
3474
+ const suffix = fallbacks.includes(key) ? "*" : "";
3475
+ return `${value ?? "-"}${suffix}`;
3476
+ };
3477
+ printEntries({
3478
+ code: preset$1.code,
3479
+ version: preset$1.code[0],
3480
+ style: preset$1.values.style,
3481
+ baseColor: formatPresetValue("baseColor", preset$1.values.baseColor),
3482
+ theme: formatPresetValue("theme", preset$1.values.theme),
3483
+ chartColor: formatPresetValue("chartColor", preset$1.values.chartColor),
3484
+ iconLibrary: formatPresetValue("iconLibrary", preset$1.values.iconLibrary),
3485
+ font: formatPresetValue("font", preset$1.values.font),
3486
+ fontHeading: formatPresetValue("fontHeading", preset$1.values.fontHeading),
3487
+ radius: formatPresetValue("radius", preset$1.values.radius),
3488
+ menuAccent: formatPresetValue("menuAccent", preset$1.values.menuAccent),
3489
+ menuColor: formatPresetValue("menuColor", preset$1.values.menuColor),
3490
+ url: getPresetUrl(preset$1.code)
3491
+ });
3492
+ if (fallbacks.length > 0) {
3493
+ logger.log("");
3494
+ logger.log(options.fallbackNote ?? " * Uses preset defaults for values that could not be inferred from the project.");
3495
+ }
3496
+ }
3497
+ const decode = new Command().name("decode").description("decode a preset code").argument("<code>", "the preset code to decode").option("--json", "output as JSON.", false).action((code, opts) => {
3498
+ try {
3499
+ const result = decodePresetCode(code);
3500
+ if (opts.json) {
3501
+ console.log(JSON.stringify(result, null, 2));
3502
+ return;
3503
+ }
3504
+ printPresetDecode(result);
3505
+ } catch (error) {
3506
+ handlePresetError(error);
3507
+ }
3508
+ });
3509
+ const url = new Command().name("url").description("get the create URL for a preset code").argument("<code>", "the preset code").action((code) => {
3510
+ try {
3511
+ logger.log(decodePresetCode(code).url);
3512
+ } catch (error) {
3513
+ handlePresetError(error);
3514
+ }
3515
+ });
3516
+ const openCommand = new Command().name("open").description("open a preset code in the browser").argument("<code>", "the preset code").action(async (code) => {
3517
+ let presetUrl;
3518
+ try {
3519
+ presetUrl = decodePresetCode(code).url;
3520
+ } catch (error) {
3521
+ handlePresetError(error);
3522
+ return;
3523
+ }
3524
+ logger.break();
3525
+ logger.log(` Opening ${presetUrl} in your browser.`);
3526
+ logger.break();
3527
+ try {
3528
+ const { default: open } = await import("open");
3529
+ await open(presetUrl);
3530
+ } catch (error) {
3531
+ const message = error instanceof Error ? error.message : String(error);
3532
+ handlePresetError(/* @__PURE__ */ new Error(`Failed to open preset URL: ${message}`));
3533
+ }
3534
+ });
3535
+ const resolve$1 = new Command().name("resolve").alias("info").description("resolve a preset from your project").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("--json", "output as JSON.", false).action(async (opts) => {
3536
+ try {
3537
+ const cwd = path.resolve(opts.cwd);
3538
+ if (!existsSync(path.resolve(cwd, "components.json")) && await isMonorepoRoot(cwd)) {
3539
+ const targets = await getMonorepoTargets(cwd);
3540
+ if (targets.length > 0) {
3541
+ if (opts.json) console.log(JSON.stringify({
3542
+ error: "monorepo_root",
3543
+ message: "You are running preset resolve from a monorepo root. Use the -c flag to specify a workspace.",
3544
+ targets: targets.map((target) => target.name)
3545
+ }, null, 2));
3546
+ else formatMonorepoMessage("preset resolve", targets, {
3547
+ binary: "bejamas",
3548
+ cwdFlag: "-c"
3549
+ });
3550
+ process.exit(1);
3551
+ }
3552
+ }
3553
+ const config = await getConfig(cwd);
3554
+ if (!config) {
3555
+ if (opts.json) {
3556
+ console.log(JSON.stringify(null, null, 2));
3557
+ return;
3558
+ }
3559
+ logger.log("No components.json found.");
3560
+ return;
3561
+ }
3562
+ const resolvedPreset = await resolveProjectPreset(config);
3563
+ if (opts.json) {
3564
+ console.log(JSON.stringify(resolvedPreset.code ? resolvedPreset : null, null, 2));
3565
+ return;
3566
+ }
3567
+ printPresetInfo(resolvedPreset);
3568
+ } catch (error) {
3569
+ handleError(error);
3570
+ }
3571
+ });
3572
+ const preset = new Command().name("preset").description("manage presets").addCommand(decode).addCommand(resolve$1).addCommand(url).addCommand(openCommand).action(() => {
3573
+ preset.outputHelp();
3574
+ });
3575
+ function resolveDecodedChartColor(decoded) {
3576
+ if (decoded.chartColor) return decoded.chartColor;
3577
+ const legacyChartColor = V1_CHART_COLOR_MAP[decoded.theme];
3578
+ if (legacyChartColor) return legacyChartColor;
3579
+ if (PRESET_CHART_COLOR_SET.has(decoded.theme)) return decoded.theme;
3580
+ }
3581
+ function printEntries(entries) {
3582
+ const maxKeyLength = Math.max(...Object.keys(entries).map((key) => key.length));
3583
+ for (const [key, value] of Object.entries(entries)) logger.log(` ${key.padEnd(maxKeyLength + 2)}${value}`);
3584
+ }
3585
+ function handlePresetError(error) {
3586
+ if (error instanceof Error) logger.error(error.message);
3587
+ process.exit(1);
3588
+ }
3589
+
2621
3590
  //#endregion
2622
3591
  //#region src/index.ts
2623
3592
  const pkg = createRequire(import.meta.url)("../package.json");
2624
3593
  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");
2625
3594
  program.addCommand(init);
2626
3595
  program.addCommand(add);
3596
+ program.addCommand(apply);
3597
+ program.addCommand(preset);
2627
3598
  program.addCommand(info);
2628
3599
  program.addCommand(docs);
2629
3600
  program.addCommand(docsBuild);