bejamas 0.2.0 → 0.2.2

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 { BASE_COLORS, extractFrontmatter, getConfig, getProjectInfo, highlighter, logger, parseJsDocMetadata, resolveUiRoot, spinner } from "./utils-DfvCox_O.js";
2
+ import { BASE_COLORS, extractFrontmatter, getConfig, getProjectInfo, getWorkspaceConfig, highlighter, logger, parseJsDocMetadata, resolveUiRoot, spinner } from "./utils-BA01hKci.js";
3
3
  import { Command } from "commander";
4
4
  import { createRequire } from "module";
5
5
  import path from "path";
@@ -527,7 +527,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
527
527
  if (process.env.BEJAMAS_DOCS_CWD) logger.info(`Docs CWD: ${process.env.BEJAMAS_DOCS_CWD}`);
528
528
  if (process.env.BEJAMAS_DOCS_OUT_DIR) logger.info(`Docs out: ${process.env.BEJAMAS_DOCS_OUT_DIR}`);
529
529
  }
530
- const mod = await import("./generate-mdx-5r1bKyim.js");
530
+ const mod = await import("./generate-mdx-CPI1OKtC.js");
531
531
  if (typeof mod.runDocsGenerator === "function") await mod.runDocsGenerator();
532
532
  else throw new Error("Failed to load docs generator. Export 'runDocsGenerator' not found.");
533
533
  } catch (err) {
@@ -770,6 +770,116 @@ async function fixAstroImports(cwd, isVerbose) {
770
770
  }
771
771
  }
772
772
 
773
+ //#endregion
774
+ //#region src/utils/reorganize-components.ts
775
+ /**
776
+ * Fetches a registry item JSON from the registry URL.
777
+ */
778
+ async function fetchRegistryItem(componentName, registryUrl) {
779
+ const url = `${registryUrl}/styles/new-york-v4/${componentName}.json`;
780
+ try {
781
+ const response = await fetch(url);
782
+ if (!response.ok) {
783
+ const fallbackUrl = `${registryUrl}/${componentName}.json`;
784
+ const fallbackResponse = await fetch(fallbackUrl);
785
+ if (!fallbackResponse.ok) return null;
786
+ return await fallbackResponse.json();
787
+ }
788
+ return await response.json();
789
+ } catch {
790
+ return null;
791
+ }
792
+ }
793
+ /**
794
+ * Extracts the subfolder name from registry file paths.
795
+ * E.g., "components/ui/avatar/Avatar.astro" → "avatar"
796
+ */
797
+ function getSubfolderFromPaths(files) {
798
+ const uiFiles = files.filter((f) => f.type === "registry:ui");
799
+ if (uiFiles.length < 2) return null;
800
+ const subfolders = /* @__PURE__ */ new Set();
801
+ for (const file of uiFiles) {
802
+ const parts = file.path.split("/");
803
+ const uiIndex = parts.indexOf("ui");
804
+ if (uiIndex !== -1 && parts.length > uiIndex + 2) subfolders.add(parts[uiIndex + 1]);
805
+ }
806
+ if (subfolders.size === 1) return Array.from(subfolders)[0];
807
+ if (uiFiles.length > 0) {
808
+ const firstPath = uiFiles[0].path;
809
+ const dirname$1 = path$1.dirname(firstPath);
810
+ const folderName = path$1.basename(dirname$1);
811
+ if (folderName && folderName !== "ui") return folderName;
812
+ }
813
+ return null;
814
+ }
815
+ /**
816
+ * Checks if a path exists.
817
+ */
818
+ async function pathExists(filePath) {
819
+ try {
820
+ await fs.access(filePath);
821
+ return true;
822
+ } catch {
823
+ return false;
824
+ }
825
+ }
826
+ /**
827
+ * Reorganizes multi-file components into correct subfolders.
828
+ * Only moves files from FLAT location to subfolder.
829
+ * Does NOT touch files already in subfolders.
830
+ * E.g., moves `uiDir/Avatar.astro` to `uiDir/avatar/Avatar.astro`.
831
+ * Returns info about moved files for display purposes.
832
+ */
833
+ async function reorganizeComponents(components, uiDir, registryUrl, verbose) {
834
+ const result = {
835
+ totalMoved: 0,
836
+ movedFiles: [],
837
+ skippedFiles: []
838
+ };
839
+ if (!uiDir || components.length === 0) return result;
840
+ for (const componentName of components) try {
841
+ const registryItem = await fetchRegistryItem(componentName, registryUrl);
842
+ if (!registryItem) {
843
+ if (verbose) logger.info(`[bejamas-ui] Could not fetch registry for ${componentName}, skipping reorganization`);
844
+ continue;
845
+ }
846
+ const subfolder = getSubfolderFromPaths(registryItem.files);
847
+ if (!subfolder) {
848
+ if (verbose) logger.info(`[bejamas-ui] ${componentName} is single-file or has no subfolder, skipping`);
849
+ continue;
850
+ }
851
+ const uiFiles = registryItem.files.filter((f) => f.type === "registry:ui");
852
+ const targetDir = path$1.join(uiDir, subfolder);
853
+ let movedCount = 0;
854
+ for (const file of uiFiles) {
855
+ const filename = path$1.basename(file.path);
856
+ const flatPath = path$1.join(uiDir, filename);
857
+ const targetPath = path$1.join(targetDir, filename);
858
+ if (!await pathExists(flatPath)) continue;
859
+ if (await pathExists(targetPath)) {
860
+ try {
861
+ await fs.unlink(flatPath);
862
+ result.skippedFiles.push(`${subfolder}/${filename}`);
863
+ if (verbose) logger.info(`[bejamas-ui] Removed flat duplicate: ${filename} (${subfolder}/${filename} exists)`);
864
+ } catch {
865
+ result.skippedFiles.push(`${subfolder}/${filename}`);
866
+ }
867
+ continue;
868
+ }
869
+ await fs.mkdir(targetDir, { recursive: true });
870
+ await fs.rename(flatPath, targetPath);
871
+ movedCount++;
872
+ result.totalMoved++;
873
+ result.movedFiles.push(`${subfolder}/${filename}`);
874
+ if (verbose) logger.info(`[bejamas-ui] Moved ${filename} → ${subfolder}/${filename}`);
875
+ }
876
+ if (movedCount > 0 && verbose) logger.info(`[bejamas-ui] Reorganized ${componentName} into ${subfolder}/`);
877
+ } catch (err) {
878
+ if (verbose) logger.warn(`[bejamas-ui] Failed to reorganize ${componentName}: ${err}`);
879
+ }
880
+ return result;
881
+ }
882
+
773
883
  //#endregion
774
884
  //#region src/commands/add.ts
775
885
  const DEFAULT_REGISTRY_URL = "https://ui.bejamas.com/r";
@@ -836,16 +946,110 @@ function extractOptionsForShadcn(rawArgv, cmd) {
836
946
  }
837
947
  return forwarded;
838
948
  }
839
- async function addComponents(packages, forwardedOptions, isVerbose) {
949
+ /** Build maps for path rewriting, handling filename collisions */
950
+ async function buildSubfolderMap(components, registryUrl) {
951
+ const filenameToSubfolders = /* @__PURE__ */ new Map();
952
+ const componentInfo = /* @__PURE__ */ new Map();
953
+ for (const componentName of components) {
954
+ const registryItem = await fetchRegistryItem(componentName, registryUrl);
955
+ if (!registryItem) continue;
956
+ const subfolder = getSubfolderFromPaths(registryItem.files);
957
+ if (!subfolder) continue;
958
+ const files = [];
959
+ for (const file of registryItem.files) if (file.type === "registry:ui") {
960
+ const filename = path$1.basename(file.path);
961
+ files.push(filename);
962
+ const subfolders = filenameToSubfolders.get(filename) || [];
963
+ subfolders.push(subfolder);
964
+ filenameToSubfolders.set(filename, subfolders);
965
+ }
966
+ componentInfo.set(subfolder, {
967
+ subfolder,
968
+ files
969
+ });
970
+ }
971
+ const uniqueMap = /* @__PURE__ */ new Map();
972
+ const sharedFilenames = /* @__PURE__ */ new Set();
973
+ filenameToSubfolders.forEach((subfolders, filename) => {
974
+ if (subfolders.length === 1) uniqueMap.set(filename, `${subfolders[0]}/${filename}`);
975
+ else sharedFilenames.add(filename);
976
+ });
977
+ return {
978
+ uniqueMap,
979
+ componentInfo,
980
+ sharedFilenames
981
+ };
982
+ }
983
+ /**
984
+ * Rewrite file paths to include correct subfolders.
985
+ * Handles shared filenames (like index.ts) by tracking current component context.
986
+ */
987
+ function rewritePaths(paths, mapResult) {
988
+ const { uniqueMap, componentInfo, sharedFilenames } = mapResult;
989
+ let currentSubfolder = null;
990
+ return paths.map((filePath) => {
991
+ const filename = path$1.basename(filePath);
992
+ const parentDir = path$1.basename(path$1.dirname(filePath));
993
+ const uniqueMapping = uniqueMap.get(filename);
994
+ if (uniqueMapping) {
995
+ const expectedSubfolder = path$1.dirname(uniqueMapping);
996
+ currentSubfolder = expectedSubfolder;
997
+ if (parentDir !== expectedSubfolder) return `${path$1.dirname(filePath)}/${uniqueMapping}`;
998
+ return filePath;
999
+ }
1000
+ if (sharedFilenames.has(filename) && currentSubfolder) {
1001
+ const expectedSubfolder = currentSubfolder;
1002
+ if (parentDir !== expectedSubfolder) return `${path$1.dirname(filePath)}/${expectedSubfolder}/${filename}`;
1003
+ }
1004
+ return filePath;
1005
+ });
1006
+ }
1007
+ /** Parse shadcn output to extract file lists (stdout has paths, stderr has headers) */
1008
+ function parseShadcnOutput(stdout, stderr) {
1009
+ const result = {
1010
+ created: [],
1011
+ updated: [],
1012
+ skipped: []
1013
+ };
1014
+ const cleanStderr = stderr.replace(/\x1b\[[0-9;]*m/g, "");
1015
+ const cleanStdout = stdout.replace(/\x1b\[[0-9;]*m/g, "");
1016
+ const createdMatch = cleanStderr.match(/Created\s+(\d+)\s+file/i);
1017
+ const updatedMatch = cleanStderr.match(/Updated\s+(\d+)\s+file/i);
1018
+ const skippedMatch = cleanStderr.match(/Skipped\s+(\d+)\s+file/i);
1019
+ const createdCount = createdMatch ? parseInt(createdMatch[1], 10) : 0;
1020
+ const updatedCount = updatedMatch ? parseInt(updatedMatch[1], 10) : 0;
1021
+ const skippedCount = skippedMatch ? parseInt(skippedMatch[1], 10) : 0;
1022
+ const allPaths = [];
1023
+ for (const line of cleanStdout.split("\n")) {
1024
+ const match = line.match(/^\s+-\s+(.+)$/);
1025
+ if (match) allPaths.push(match[1].trim());
1026
+ }
1027
+ for (const line of cleanStderr.split("\n")) {
1028
+ const match = line.match(/^\s+-\s+(.+)$/);
1029
+ if (match) {
1030
+ const filePath = match[1].trim();
1031
+ if (!allPaths.includes(filePath)) allPaths.push(filePath);
1032
+ }
1033
+ }
1034
+ let idx = 0;
1035
+ for (let i = 0; i < createdCount && idx < allPaths.length; i++) result.created.push(allPaths[idx++]);
1036
+ for (let i = 0; i < updatedCount && idx < allPaths.length; i++) result.updated.push(allPaths[idx++]);
1037
+ for (let i = 0; i < skippedCount && idx < allPaths.length; i++) result.skipped.push(allPaths[idx++]);
1038
+ return result;
1039
+ }
1040
+ async function addComponents(packages, forwardedOptions, isVerbose, isSilent, subfolderMapResult) {
840
1041
  const runner = await getPackageRunner(process.cwd());
841
1042
  const env = {
842
1043
  ...process.env,
843
1044
  REGISTRY_URL: process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL
844
1045
  };
1046
+ const autoFlags = [];
1047
+ if (!forwardedOptions.includes("--yes")) autoFlags.push("--yes");
845
1048
  const baseArgs = [
846
1049
  "shadcn@latest",
847
1050
  "add",
848
1051
  ...packages,
1052
+ ...autoFlags,
849
1053
  ...forwardedOptions
850
1054
  ];
851
1055
  let cmd = "npx";
@@ -861,12 +1065,33 @@ async function addComponents(packages, forwardedOptions, isVerbose) {
861
1065
  args = ["-y", ...baseArgs];
862
1066
  }
863
1067
  if (isVerbose) logger.info(`[bejamas-ui] ${cmd} ${args.join(" ")}`);
1068
+ const registrySpinner = spinner("Checking registry.", { silent: isSilent });
1069
+ registrySpinner.start();
864
1070
  try {
865
- await execa(cmd, args, {
866
- stdio: "inherit",
867
- env
1071
+ const result = await execa(cmd, args, {
1072
+ env,
1073
+ input: "n\nn\nn\nn\nn\nn\nn\nn\nn\nn\n",
1074
+ stdout: "pipe",
1075
+ stderr: "pipe",
1076
+ reject: false
868
1077
  });
1078
+ registrySpinner.succeed();
1079
+ spinner("Installing components.", { silent: isSilent }).succeed();
1080
+ const stdout = result.stdout || "";
1081
+ const stderr = result.stderr || "";
1082
+ if (isVerbose) {
1083
+ logger.info(`[bejamas-ui] Raw stdout: ${stdout}`);
1084
+ logger.info(`[bejamas-ui] Raw stderr: ${stderr}`);
1085
+ }
1086
+ const parsed = parseShadcnOutput(stdout, stderr);
1087
+ if (result.exitCode !== 0) {
1088
+ if (result.stderr) logger.error(result.stderr);
1089
+ process.exit(result.exitCode);
1090
+ }
1091
+ return parsed;
869
1092
  } catch (err) {
1093
+ registrySpinner.fail();
1094
+ logger.error("Failed to add components");
870
1095
  process.exit(1);
871
1096
  }
872
1097
  }
@@ -875,8 +1100,60 @@ const add = new Command().name("add").description("Add components via shadcn@lat
875
1100
  const verbose = Boolean(root?.opts?.().verbose);
876
1101
  const rawArgv = process.argv.slice(2);
877
1102
  const forwardedOptions = extractOptionsForShadcn(rawArgv, cmd);
878
- const cwd = (typeof cmd.optsWithGlobals === "function" ? cmd.optsWithGlobals() : cmd.opts?.() ?? {}).cwd || process.cwd();
879
- await addComponents(packages || [], forwardedOptions, verbose);
1103
+ const opts = typeof cmd.optsWithGlobals === "function" ? cmd.optsWithGlobals() : cmd.opts?.() ?? {};
1104
+ const cwd = opts.cwd || process.cwd();
1105
+ const config = await getConfig(cwd);
1106
+ const registryUrl = process.env.REGISTRY_URL || DEFAULT_REGISTRY_URL;
1107
+ let uiDir = config?.resolvedPaths?.ui || "";
1108
+ let uiConfig = config;
1109
+ if (config) {
1110
+ const workspaceConfig = await getWorkspaceConfig(config);
1111
+ if (workspaceConfig?.ui) {
1112
+ uiConfig = workspaceConfig.ui;
1113
+ uiDir = uiConfig.resolvedPaths?.ui || uiDir;
1114
+ }
1115
+ }
1116
+ if (verbose) {
1117
+ logger.info(`[bejamas-ui] cwd: ${cwd}`);
1118
+ logger.info(`[bejamas-ui] uiDir: ${uiDir}`);
1119
+ logger.info(`[bejamas-ui] aliases.ui: ${uiConfig?.aliases?.ui || "not set"}`);
1120
+ }
1121
+ const isSilent = opts.silent || false;
1122
+ const componentsToAdd = packages || [];
1123
+ const totalComponents = componentsToAdd.length;
1124
+ for (let i = 0; i < componentsToAdd.length; i++) {
1125
+ const component = componentsToAdd[i];
1126
+ if (totalComponents > 1 && !isSilent) {
1127
+ logger.break();
1128
+ logger.info(highlighter.info(`[${i + 1}/${totalComponents}]`) + ` Adding ${highlighter.success(component)}...`);
1129
+ }
1130
+ const subfolderMapResult = await buildSubfolderMap([component], registryUrl);
1131
+ const parsed = await addComponents([component], forwardedOptions, verbose, isSilent, subfolderMapResult);
1132
+ let skippedCount = 0;
1133
+ if (uiDir) skippedCount = (await reorganizeComponents([component], uiDir, registryUrl, verbose)).skippedFiles.length;
1134
+ if (!isSilent) {
1135
+ uiDir && path$1.relative(cwd, uiDir);
1136
+ const actuallyCreated = Math.max(0, parsed.created.length - skippedCount);
1137
+ if (actuallyCreated > 0) {
1138
+ const createdPaths = rewritePaths(parsed.created.slice(0, actuallyCreated), subfolderMapResult);
1139
+ logger.success(`Created ${createdPaths.length} file${createdPaths.length > 1 ? "s" : ""}:`);
1140
+ for (const file of createdPaths) logger.log(` ${highlighter.info("-")} ${file}`);
1141
+ }
1142
+ if (parsed.updated.length > 0) {
1143
+ const uniqueUpdated = Array.from(new Set(parsed.updated));
1144
+ const updatedPaths = rewritePaths(uniqueUpdated, subfolderMapResult);
1145
+ logger.info(`Updated ${updatedPaths.length} file${updatedPaths.length > 1 ? "s" : ""}:`);
1146
+ for (const file of updatedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
1147
+ }
1148
+ if (skippedCount > 0) logger.info(`Skipped ${skippedCount} file${skippedCount > 1 ? "s" : ""}: (already exists)`);
1149
+ if (parsed.skipped.length > 0) {
1150
+ const skippedPaths = rewritePaths(parsed.skipped, subfolderMapResult);
1151
+ logger.info(`Skipped ${skippedPaths.length} file${skippedPaths.length > 1 ? "s" : ""}: (use --overwrite)`);
1152
+ for (const file of skippedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
1153
+ }
1154
+ if (actuallyCreated === 0 && parsed.updated.length === 0 && skippedCount === 0 && parsed.skipped.length === 0) logger.info("Already up to date.");
1155
+ }
1156
+ }
880
1157
  await fixAstroImports(cwd, verbose);
881
1158
  });
882
1159