bejamas 0.4.1 → 0.5.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 { $ 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-CyYWo_qS.js";
2
+ import { $ as PRESET_MENU_COLORS, A as SEMANTIC_ICON_NAMES, B as getFontValue, C as BEJAMAS_COMPONENTS_SCHEMA_URL, D as ICON_LIBRARIES, E as resolveRegistryUrl, F as RTL_LANGUAGE_VALUES, G as normalizeDesignSystemConfig, H as getRadiusValue, I as designSystemConfigSchema, J as PRESET_CHART_COLORS, K as DEFAULT_PRESET_CONFIG, L as getDocumentDirection, M as getSemanticIconNameFromLucidePath, N as renderSemanticIconSvgWithAttributeString, O as buildRegistryTheme, P as DEFAULT_DESIGN_SYSTEM_CONFIG, Q as PRESET_MENU_ACCENTS, R as getDocumentLanguage, S as highlighter, T as buildUiUrl, U as getStyleDefaultRadius, V as getHeadingFontValue, W as getStyleId, X as PRESET_FONT_HEADINGS, Y as PRESET_FONTS, Z as PRESET_ICON_LIBRARIES, _ as findCommonRoot, at as encodePreset, b as getProjectInfo, d as parseJsDocMetadata, et as PRESET_RADII, g as spinner, it as decodePreset, j as getSemanticIconNameFromLucideExport, k as ICON_LIBRARY_COLLECTIONS, nt as PRESET_THEMES, o as extractFrontmatter, ot as isPresetCode, p as resolveUiRoot, q as PRESET_BASE_COLORS, rt as V1_CHART_COLOR_MAP, st as fonts, tt as PRESET_STYLES, v as getConfig, w as BASE_COLORS, x as logger, y as getWorkspaceConfig, z as getFontPackageName } from "./utils-BEqgES6q.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";
@@ -18,6 +18,8 @@ import { execa } from "execa";
18
18
  import prompts from "prompts";
19
19
  import * as tar from "tar";
20
20
  import os$1 from "node:os";
21
+ import { stripVTControlCharacters } from "node:util";
22
+ import { StringDecoder } from "node:string_decoder";
21
23
 
22
24
  //#region src/registry/context.ts
23
25
  let context = { headers: {} };
@@ -25,17 +27,12 @@ function clearRegistryContext() {
25
27
  context.headers = {};
26
28
  }
27
29
 
28
- //#endregion
29
- //#region src/utils/errors.ts
30
- const MISSING_DIR_OR_EMPTY_PROJECT = "1";
31
- const MISSING_CONFIG = "3";
32
-
33
30
  //#endregion
34
31
  //#region src/preflights/preflight-init.ts
35
32
  async function preFlightInit(options) {
36
33
  const errors = {};
37
34
  if (!fs.existsSync(options.cwd) || !fs.existsSync(path$1.resolve(options.cwd, "package.json"))) {
38
- errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
35
+ errors["1"] = true;
39
36
  return {
40
37
  errors,
41
38
  projectInfo: null
@@ -62,8 +59,8 @@ const ASTRO_CONFIG_CANDIDATES = [
62
59
  const ASTRO_LAYOUT_CANDIDATES = ["src/layouts/Layout.astro", "apps/web/src/layouts/Layout.astro"];
63
60
  const STARLIGHT_HEAD_RELATIVE_PATH = "apps/docs/src/components/Head.astro";
64
61
  const ASTRO_FONT_PROVIDERS = ["fontsource", "google"];
65
- const ASTRO_FONTSOURCE_FONT_NAMES = new Set(["geist", "geist-mono"]);
66
- const MANAGED_BODY_FONT_CLASSES = new Set(["font-mono", "font-serif"]);
62
+ const ASTRO_FONTSOURCE_FONT_NAMES = /* @__PURE__ */ new Set(["geist", "geist-mono"]);
63
+ const MANAGED_BODY_FONT_CLASSES = /* @__PURE__ */ new Set(["font-mono", "font-serif"]);
67
64
  function compactSource$1(source) {
68
65
  return source.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
69
66
  }
@@ -71,23 +68,25 @@ function escapeRegExp$2(value) {
71
68
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
72
69
  }
73
70
  function upsertImport(source, moduleName, importedName, fallbackNames = []) {
74
- const importPattern = /* @__PURE__ */ new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']${escapeRegExp$2(moduleName)}["'];?`);
71
+ const importPattern = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']${escapeRegExp$2(moduleName)}["'];?`);
75
72
  if (importPattern.test(source)) return source.replace(importPattern, (_match, importsSource) => {
76
73
  const imports = importsSource.split(",").map((value) => value.trim()).filter(Boolean);
77
74
  if (!imports.includes(importedName)) imports.push(importedName);
78
75
  for (const name of fallbackNames) if (!imports.includes(name)) imports.unshift(name);
79
76
  return `import { ${Array.from(new Set(imports)).join(", ")} } from "${moduleName}";`;
80
77
  });
81
- const importLine = `import { ${Array.from(new Set([...fallbackNames, importedName])).join(", ")} } from "${moduleName}";`;
78
+ const importLine = `import { ${Array.from(/* @__PURE__ */ new Set([...fallbackNames, importedName])).join(", ")} } from "${moduleName}";`;
82
79
  const frontmatterEnd = source.indexOf("---", 3);
83
80
  if (source.startsWith("---") && frontmatterEnd !== -1) return `${source.slice(0, frontmatterEnd).trimEnd()}\n${importLine}\n${source.slice(frontmatterEnd)}`;
84
81
  return `${importLine}\n${source}`;
85
82
  }
86
83
  function buildAstroConfigFontBlock(fontsToSerialize) {
87
- 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}`;
84
+ const body = 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`;
85
+ return `${ASTRO_CONFIG_BLOCK_START}\n/** @type {NonNullable<import("astro").AstroUserConfig["fonts"]>} */\nconst ${ASTRO_FONT_CONSTANT} = [${body}];\n${ASTRO_CONFIG_BLOCK_END}`;
88
86
  }
89
87
  function buildAstroLayoutFontBlock(fontsToSerialize) {
90
- return `${ASTRO_LAYOUT_BLOCK_START}\n${fontsToSerialize.map((font) => `<Font cssVariable="${font.cssVariable}" />`).join("\n")}\n${ASTRO_LAYOUT_BLOCK_END}`;
88
+ const body = fontsToSerialize.map((font) => `<Font cssVariable="${font.cssVariable}" />`).join("\n");
89
+ return `${ASTRO_LAYOUT_BLOCK_START}\n${body}\n${ASTRO_LAYOUT_BLOCK_END}`;
91
90
  }
92
91
  function bodyFontClassFromCssVariable(cssVariable) {
93
92
  if (cssVariable === "--font-mono" || cssVariable === "--font-serif") return fontClassFromCssVariable(cssVariable);
@@ -102,14 +101,14 @@ function upsertManagedBodyFontClass(source, cssVariable) {
102
101
  const classMatch = attrs.match(classPattern);
103
102
  const quote = classMatch?.[1] ?? "\"";
104
103
  const existingClasses = (classMatch?.[2] ?? "").split(/\s+/).filter(Boolean).filter((className) => !MANAGED_BODY_FONT_CLASSES.has(className));
105
- const nextClasses = bodyClass ? Array.from(new Set([...existingClasses, bodyClass])) : existingClasses;
104
+ const nextClasses = bodyClass ? Array.from(/* @__PURE__ */ new Set([...existingClasses, bodyClass])) : existingClasses;
106
105
  let nextAttrs = attrs.replace(classPattern, "");
107
106
  if (nextClasses.length > 0) nextAttrs = `${nextAttrs} class=${quote}${nextClasses.join(" ")}${quote}`;
108
107
  return `<body${nextAttrs}${selfClosing}>`;
109
108
  });
110
109
  }
111
110
  function parseManagedAstroFonts(source) {
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)}`);
111
+ const markerPattern = 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)}`);
113
112
  const match = source.match(markerPattern);
114
113
  if (!match) return [];
115
114
  return (match[1].match(/\{[\s\S]*?\}/g) ?? []).map((entry) => {
@@ -325,7 +324,7 @@ const CREATE_BLOCK_START = "/* bejamas:create:start */";
325
324
  const CREATE_BLOCK_END = "/* bejamas:create:end */";
326
325
  const SHADCN_TAILWIND_IMPORT = "@import \"shadcn/tailwind.css\";";
327
326
  const BEJAMAS_TAILWIND_IMPORT = "@import \"bejamas/tailwind.css\";";
328
- const MANAGED_TAILWIND_IMPORTS = new Set([SHADCN_TAILWIND_IMPORT, BEJAMAS_TAILWIND_IMPORT]);
327
+ const MANAGED_TAILWIND_IMPORTS = /* @__PURE__ */ new Set([SHADCN_TAILWIND_IMPORT, BEJAMAS_TAILWIND_IMPORT]);
329
328
  const MANAGED_ICON_PACKAGES = new Set(ICON_LIBRARIES.map((library) => library.packageName));
330
329
  const FALLBACK_ICON_PACKAGE_VERSION = "latest";
331
330
  const TEMPLATE_APP_UI_IMPORT = "import { appUi } from \"@/i18n/ui\";";
@@ -740,7 +739,7 @@ function transformManagedTailwindImportCss(source) {
740
739
  }
741
740
  async function patchComponentsJson(filepath, config) {
742
741
  const current = await fs.readJson(filepath);
743
- const { rsc: _rsc, tsx: _tsx,...normalizedCurrent } = current;
742
+ const { rsc: _rsc, tsx: _tsx, ...normalizedCurrent } = current;
744
743
  const next = {
745
744
  ...normalizedCurrent,
746
745
  $schema: BEJAMAS_COMPONENTS_SCHEMA_URL,
@@ -865,8 +864,8 @@ async function patchLayoutFile(filepath, config) {
865
864
  const current = await fs.readFile(filepath, "utf8");
866
865
  if (current.includes("from \"@/i18n/ui\"")) return;
867
866
  if (config.rtl) {
868
- const next$1 = transformTemplateLayoutToI18n(current);
869
- if (next$1 !== current) await fs.writeFile(filepath, next$1, "utf8");
867
+ const next = transformTemplateLayoutToI18n(current);
868
+ if (next !== current) await fs.writeFile(filepath, next, "utf8");
870
869
  return;
871
870
  }
872
871
  const nextLanguage = getDocumentLanguage(config);
@@ -1012,7 +1011,8 @@ function rewriteAstroIcons(content, iconLibrary) {
1012
1011
  function renderSemanticIconUsage(original, library, attributeString = "") {
1013
1012
  const semanticName = attributeString.match(/\sname=(["'])([^"']+)\1/)?.[2];
1014
1013
  if (!semanticName || !SEMANTIC_ICON_NAMES.includes(semanticName)) return original;
1015
- return renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString.replace(/\sname=(["'])[^"']+\1/g, "").replace(/\slibrary=(["'])[^"']+\1/g, "").replace(/\sdata-icon-library=(["'])[^"']+\1/g, "").trim());
1014
+ const normalizedAttributeString = attributeString.replace(/\sname=(["'])[^"']+\1/g, "").replace(/\slibrary=(["'])[^"']+\1/g, "").replace(/\sdata-icon-library=(["'])[^"']+\1/g, "").trim();
1015
+ return renderSemanticIconSvgWithAttributeString(library, semanticName, normalizedAttributeString);
1016
1016
  }
1017
1017
 
1018
1018
  //#endregion
@@ -1064,6 +1064,15 @@ function rewriteAstroMenus(content, menuColor) {
1064
1064
  //#endregion
1065
1065
  //#region src/utils/astro-imports.ts
1066
1066
  function updateImportAliases(moduleSpecifier, config, isRemote = false) {
1067
+ const scope = config.aliases.components.split("/")[0];
1068
+ for (const [segment, alias] of [
1069
+ ["lib/utils", config.aliases.utils],
1070
+ ["lib", config.aliases.lib],
1071
+ ["hooks", config.aliases.hooks]
1072
+ ]) {
1073
+ if (!alias) continue;
1074
+ for (const prefix of [`@/${segment}`, `${scope}/${segment}`]) if (moduleSpecifier === prefix || moduleSpecifier.startsWith(`${prefix}/`)) return alias + moduleSpecifier.slice(prefix.length);
1075
+ }
1067
1076
  if (!moduleSpecifier.startsWith("@/") && !isRemote) return moduleSpecifier;
1068
1077
  let specifier = moduleSpecifier;
1069
1078
  if (isRemote && specifier.startsWith("@/")) specifier = specifier.replace(/^@\//, `@/registry/${config.style || "bejamas-juno"}/`);
@@ -1088,6 +1097,7 @@ function rewriteAstroImports(content, config) {
1088
1097
  if (finalSpec === specifier) return full;
1089
1098
  return full.replace(specifier, finalSpec);
1090
1099
  });
1100
+ updated = updated.replace(/(?:export\s+(?:type\s+)?(?:\{[^}]*\}|\*)\s+from\s*|import\s*\(\s*)["']([^"']+)["']/g, (full, specifier) => full.replace(specifier, updateImportAliases(specifier, config)));
1091
1101
  updated = updated.replace(/import\s+["']([^"']+)["']/g, (full, specifier) => {
1092
1102
  const next = updateImportAliases(specifier, config, false);
1093
1103
  if (next === specifier) return full;
@@ -1096,25 +1106,45 @@ function rewriteAstroImports(content, config) {
1096
1106
  updated = rewriteAstroMenus(updated, config.menuColor);
1097
1107
  return rewriteAstroIcons(updated, config.iconLibrary);
1098
1108
  }
1099
- async function fixAstroImports(cwd, isVerbose) {
1100
- const config = await getConfig(cwd);
1109
+ function getConfiguredSourceRoots(config) {
1110
+ return Array.from(new Set([
1111
+ config.resolvedPaths.components,
1112
+ config.resolvedPaths.ui,
1113
+ config.resolvedPaths.lib,
1114
+ config.resolvedPaths.hooks
1115
+ ].filter((root) => Boolean(root))));
1116
+ }
1117
+ function isPathWithin(filePath, root) {
1118
+ const relative = path.relative(root, filePath);
1119
+ return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
1120
+ }
1121
+ async function fixAstroImports(cwd, isVerbose, targetConfig, scope = { kind: "configured-roots" }) {
1122
+ const config = targetConfig ?? await getConfig(cwd);
1101
1123
  if (!config) return;
1102
- const searchRoots = new Set([config.resolvedPaths.components, config.resolvedPaths.ui]);
1103
- for (const root of Array.from(searchRoots)) {
1104
- if (!root) continue;
1105
- const astroFiles = await fg("**/*.astro", {
1106
- cwd: root,
1107
- absolute: true,
1108
- dot: false
1109
- });
1110
- for (const filePath of astroFiles) {
1111
- const original = await fs$1.readFile(filePath, "utf8");
1112
- const rewritten = rewriteAstroImports(original, config);
1113
- if (rewritten === original) continue;
1114
- await fs$1.writeFile(filePath, rewritten, "utf8");
1115
- if (isVerbose) logger.info(`[bejamas-ui] fixed imports in ${path.relative(cwd, filePath)}`);
1124
+ const files = /* @__PURE__ */ new Set();
1125
+ if (scope.kind === "files") for (const filePath of scope.paths) {
1126
+ const absolutePath = path.resolve(cwd, filePath);
1127
+ if (!/\.(?:astro|ts|js)$/.test(absolutePath)) continue;
1128
+ if ((await fs$1.stat(absolutePath).catch(() => null))?.isFile()) files.add(absolutePath);
1129
+ }
1130
+ else {
1131
+ const excluded = new Set((scope.exclude ?? []).map((filePath) => path.resolve(cwd, filePath)));
1132
+ for (const root of getConfiguredSourceRoots(config)) {
1133
+ const matches = await fg("**/*.{astro,ts,js}", {
1134
+ cwd: root,
1135
+ absolute: true,
1136
+ dot: false
1137
+ });
1138
+ for (const filePath of matches) if (!excluded.has(path.resolve(filePath))) files.add(filePath);
1116
1139
  }
1117
1140
  }
1141
+ for (const filePath of files) {
1142
+ const original = await fs$1.readFile(filePath, "utf8");
1143
+ const rewritten = rewriteAstroImports(original, config);
1144
+ if (rewritten === original) continue;
1145
+ await fs$1.writeFile(filePath, rewritten, "utf8");
1146
+ if (isVerbose) logger.info(`[bejamas-ui] fixed imports in ${path.relative(cwd, filePath)}`);
1147
+ }
1118
1148
  }
1119
1149
 
1120
1150
  //#endregion
@@ -1235,7 +1265,7 @@ const TEMPLATE_DIRNAME = {
1235
1265
  "astro-monorepo": "monorepo-astro",
1236
1266
  "astro-with-component-docs-monorepo": "monorepo-astro-with-docs"
1237
1267
  };
1238
- const TEMPLATE_EXCLUDED_BASENAMES = new Set(["node_modules", ".astro"]);
1268
+ const TEMPLATE_EXCLUDED_BASENAMES = /* @__PURE__ */ new Set(["node_modules", ".astro"]);
1239
1269
  const DEFAULT_TEMPLATE_REPOSITORY = "bejamas/ui";
1240
1270
  const DEFAULT_TEMPLATE_REF = "main";
1241
1271
  function resolveLocalTemplatesDir(env = process.env) {
@@ -1498,12 +1528,32 @@ async function getInstalledUiComponents(cwd) {
1498
1528
 
1499
1529
  //#endregion
1500
1530
  //#region src/utils/reorganize-components.ts
1531
+ const BEJAMAS_REGISTRY_NAMESPACE = "@bejamas/";
1532
+ /**
1533
+ * Resolve the Bejamas item name behind a user-provided specifier. Returns null
1534
+ * for URLs and third-party namespaces, which shadcn resolves on its own.
1535
+ */
1536
+ function resolveBejamasRegistryItemName(specifier) {
1537
+ const name = specifier.startsWith("@bejamas/") ? specifier.slice(9) : specifier;
1538
+ return /^[a-z0-9][a-z0-9-]*$/.test(name) ? name : null;
1539
+ }
1540
+ function isRegistryItemUrl(specifier) {
1541
+ return /^https?:\/\//.test(specifier);
1542
+ }
1501
1543
  async function fetchRegistryItem(componentName, registryUrl, style = "bejamas-juno") {
1502
- const url$1 = `${registryUrl}/styles/${style}/${componentName}.json`;
1544
+ if (isRegistryItemUrl(componentName)) try {
1545
+ const response = await fetch(componentName);
1546
+ return response.ok ? await response.json() : null;
1547
+ } catch {
1548
+ return null;
1549
+ }
1550
+ const itemName = resolveBejamasRegistryItemName(componentName);
1551
+ if (!itemName) return null;
1552
+ const url = `${registryUrl}/styles/${style}/${itemName}.json`;
1503
1553
  try {
1504
- const response = await fetch(url$1);
1554
+ const response = await fetch(url);
1505
1555
  if (!response.ok) {
1506
- const fallbackUrl = `${registryUrl}/${componentName}.json`;
1556
+ const fallbackUrl = `${registryUrl}/${itemName}.json`;
1507
1557
  const fallbackResponse = await fetch(fallbackUrl);
1508
1558
  if (!fallbackResponse.ok) return null;
1509
1559
  return await fallbackResponse.json();
@@ -1516,7 +1566,7 @@ async function fetchRegistryItem(componentName, registryUrl, style = "bejamas-ju
1516
1566
  function getSubfolderFromPaths(files) {
1517
1567
  if (!files || files.length === 0) return null;
1518
1568
  const uiFiles = files.filter((file) => file.type === "registry:ui");
1519
- if (uiFiles.length < 2) return null;
1569
+ if (uiFiles.length === 0) return null;
1520
1570
  const subfolders = /* @__PURE__ */ new Set();
1521
1571
  for (const file of uiFiles) {
1522
1572
  const parts = file.path.split("/");
@@ -1525,8 +1575,8 @@ function getSubfolderFromPaths(files) {
1525
1575
  }
1526
1576
  if (subfolders.size === 1) return Array.from(subfolders)[0];
1527
1577
  if (uiFiles.length > 0) {
1528
- const dirname$1 = path.dirname(uiFiles[0].path);
1529
- const folderName = path.basename(dirname$1);
1578
+ const dirname = path.dirname(uiFiles[0].path);
1579
+ const folderName = path.basename(dirname);
1530
1580
  if (folderName && folderName !== "ui") return folderName;
1531
1581
  }
1532
1582
  return null;
@@ -1556,7 +1606,7 @@ function resolveShadcnUiRelativePath(filePath, uiDir) {
1556
1606
  function shouldReorganizeRegistryUiFiles(files, uiDir) {
1557
1607
  if (!uiDir) return false;
1558
1608
  if (!getSubfolderFromPaths(files)) return false;
1559
- return files.filter((file) => file.type === "registry:ui").some((file) => {
1609
+ return (files ?? []).filter((file) => file.type === "registry:ui").some((file) => {
1560
1610
  return !resolveShadcnUiRelativePath(file.path, uiDir).includes("/");
1561
1611
  });
1562
1612
  }
@@ -1577,6 +1627,7 @@ async function reorganizeRegistryUiFiles(files, uiDir, verbose, overwriteExistin
1577
1627
  const targetDir = path.join(uiDir, subfolder);
1578
1628
  for (const file of uiFiles) {
1579
1629
  const filename = path.basename(file.path);
1630
+ if (filename === "index.ts") continue;
1580
1631
  const flatPath = path.join(uiDir, filename);
1581
1632
  const targetPath = path.join(targetDir, filename);
1582
1633
  if (!await pathExists(flatPath)) continue;
@@ -1605,39 +1656,82 @@ async function reorganizeRegistryUiFiles(files, uiDir, verbose, overwriteExistin
1605
1656
  result.movedFiles.push(`${subfolder}/${filename}`);
1606
1657
  if (verbose) logger.info(`[bejamas-ui] Moved ${filename} -> ${subfolder}/${filename}`);
1607
1658
  }
1659
+ if (await pathExists(targetDir)) await repairComponentBarrel(targetDir, files, overwriteExisting);
1608
1660
  return result;
1609
1661
  }
1662
+ async function repairComponentBarrel(targetDir, files, overwriteExisting = false) {
1663
+ const localFiles = (await fs$1.readdir(targetDir)).filter((name) => name.endsWith(".astro")).sort();
1664
+ if (!localFiles.length) return;
1665
+ const indexPath = path.join(targetDir, "index.ts");
1666
+ const existing = await fs$1.readFile(indexPath, "utf8").catch(() => "");
1667
+ const registryBarrel = files.find((file) => path.basename(file.path) === "index.ts")?.content ?? "";
1668
+ const cleaned = (overwriteExisting || !existing ? registryBarrel : existing).replace(/export\s*\{[^}]*\}\s*from\s*["'](\.\/[^"']+\.astro)["'];?/g, (statement, specifier) => localFiles.includes(specifier.slice(2)) ? statement : "").trim();
1669
+ const next = [cleaned, ...localFiles.filter((file) => {
1670
+ const name = path.basename(file, ".astro");
1671
+ return !new RegExp(`default\\s+as\\s+${name}\\b`).test(cleaned);
1672
+ }).map((file) => `export { default as ${path.basename(file, ".astro")} } from "./${file}";`)].filter(Boolean).join("\n") + "\n";
1673
+ if (next !== existing) await fs$1.writeFile(indexPath, next);
1674
+ }
1675
+ /** Resolve dependencies as well as the requested item before repairing output. */
1676
+ async function fetchRegistryTree(components, registryUrl, style = "bejamas-juno") {
1677
+ const seen = /* @__PURE__ */ new Set();
1678
+ const items = [];
1679
+ async function visit(name) {
1680
+ if (seen.has(name)) return;
1681
+ seen.add(name);
1682
+ if (!isRegistryItemUrl(name) && !resolveBejamasRegistryItemName(name)) return;
1683
+ const item = await fetchRegistryItem(name, registryUrl, style);
1684
+ if (!item) throw new Error(`Unable to load registry item ${name} for installation validation.`);
1685
+ for (const dependency of item.registryDependencies ?? []) await visit(dependency);
1686
+ items.push(item);
1687
+ }
1688
+ for (const name of components) await visit(name);
1689
+ return items;
1690
+ }
1610
1691
  async function reorganizeComponents(components, uiDir, registryUrl, verbose, style = "bejamas-juno", overwriteExisting = false) {
1692
+ return reorganizeRegistryItems(uiDir && components.length ? await fetchRegistryTree(components, registryUrl, style) : [], uiDir, verbose, overwriteExisting);
1693
+ }
1694
+ /** Reuse the registry tree already resolved for this installation. */
1695
+ async function reorganizeRegistryItems(items, uiDir, verbose, overwriteExisting = false) {
1611
1696
  const result = {
1612
1697
  totalMoved: 0,
1613
1698
  movedFiles: [],
1614
1699
  skippedFiles: []
1615
1700
  };
1616
- if (!uiDir || components.length === 0) return result;
1617
- for (const componentName of components) try {
1618
- const registryItem = await fetchRegistryItem(componentName, registryUrl, style);
1619
- if (!registryItem) {
1620
- if (verbose) logger.info(`[bejamas-ui] Could not fetch registry for ${componentName}, skipping reorganization`);
1621
- continue;
1622
- }
1623
- if (!shouldReorganizeRegistryUiFiles(registryItem.files, uiDir)) continue;
1624
- const componentResult = await reorganizeRegistryUiFiles(registryItem.files, uiDir, verbose, overwriteExisting);
1701
+ if (!items.some((item) => shouldReorganizeRegistryUiFiles(item.files, uiDir))) return result;
1702
+ for (const item of items) {
1703
+ if (!shouldReorganizeRegistryUiFiles(item.files, uiDir)) continue;
1704
+ const componentResult = await reorganizeRegistryUiFiles(item.files, uiDir, verbose, overwriteExisting);
1625
1705
  result.totalMoved += componentResult.totalMoved;
1626
1706
  result.movedFiles.push(...componentResult.movedFiles);
1627
1707
  result.skippedFiles.push(...componentResult.skippedFiles);
1628
- if (componentResult.totalMoved > 0 && verbose) {
1629
- const subfolder = getSubfolderFromPaths(registryItem.files);
1630
- logger.info(`[bejamas-ui] Reorganized ${componentName} into ${subfolder}/`);
1631
- }
1632
- } catch (err) {
1633
- if (verbose) logger.warn(`[bejamas-ui] Failed to reorganize ${componentName}: ${err}`);
1634
1708
  }
1709
+ const flatIndex = path.join(uiDir, "index.ts");
1710
+ const flatContent = await fs$1.readFile(flatIndex, "utf8").catch(() => null);
1711
+ if (flatContent && items.some((item) => item.files?.some((file) => file.type === "registry:ui" && path.basename(file.path) === "index.ts" && file.content.trim() === flatContent.trim()))) await fs$1.unlink(flatIndex);
1635
1712
  return result;
1636
1713
  }
1714
+ /** Update legacy template exports once the installed UI uses nested barrels. */
1715
+ async function repairUiPackageExports(uiDir, packageRoot) {
1716
+ const entries = await fs$1.readdir(uiDir, { withFileTypes: true }).catch(() => []);
1717
+ if (!entries.some((entry) => entry.isDirectory()) || entries.some((entry) => entry.name.endsWith(".astro"))) return;
1718
+ const packagePath = path.join(packageRoot, "package.json");
1719
+ const manifest = JSON.parse(await fs$1.readFile(packagePath, "utf8"));
1720
+ if (!manifest.exports || typeof manifest.exports !== "object") return;
1721
+ const relativeUi = path.relative(packageRoot, uiDir).split(path.sep).join("/");
1722
+ if (relativeUi.startsWith("../")) return;
1723
+ const prefix = `./${relativeUi}`;
1724
+ let changed = false;
1725
+ for (const [specifier, target] of Object.entries(manifest.exports)) if (target === `${prefix}/*.astro` || target === `${prefix}/*.ts`) {
1726
+ manifest.exports[specifier] = `${prefix}/*/index.ts`;
1727
+ changed = true;
1728
+ }
1729
+ if (changed) await fs$1.writeFile(packagePath, JSON.stringify(manifest, null, 2) + "\n");
1730
+ }
1637
1731
 
1638
1732
  //#endregion
1639
1733
  //#region src/utils/shadcn-cli.ts
1640
- const PINNED_SHADCN_VERSION = "4.6.0";
1734
+ const PINNED_SHADCN_VERSION = "4.21.0";
1641
1735
  const PINNED_SHADCN_PACKAGE = `shadcn@${PINNED_SHADCN_VERSION}`;
1642
1736
  const PINNED_SHADCN_EXEC_PREFIX = path.join(os$1.tmpdir(), "bejamas-shadcn", PINNED_SHADCN_VERSION);
1643
1737
  async function ensurePinnedShadcnExecPrefix() {
@@ -1772,32 +1866,32 @@ function ensureShadcnReinstallFlag(forwardedOptions, shouldReinstall) {
1772
1866
  function extractOptionsForShadcnInit(rawArgv, cmd) {
1773
1867
  if (typeof cmd.getOptionValueSource === "function") {
1774
1868
  const opts = cmd.optsWithGlobals();
1775
- const forwarded$1 = [];
1869
+ const forwarded = [];
1776
1870
  const getSource = (key) => cmd.getOptionValueSource(key);
1777
1871
  const addBoolean = (key, flag, negateFlag) => {
1778
1872
  if (getSource(key) !== "cli") return;
1779
1873
  const value = opts[key];
1780
1874
  if (typeof value !== "boolean") return;
1781
- if (value) forwarded$1.push(flag);
1782
- else if (negateFlag) forwarded$1.push(negateFlag);
1875
+ if (value) forwarded.push(flag);
1876
+ else if (negateFlag) forwarded.push(negateFlag);
1783
1877
  };
1784
1878
  addBoolean("yes", "--yes");
1785
1879
  addBoolean("force", "--force");
1786
1880
  addBoolean("silent", "--silent");
1787
1881
  addBoolean("reinstall", "--reinstall", "--no-reinstall");
1788
- const initIndex$1 = rawArgv.findIndex((arg) => arg === "init");
1789
- if (initIndex$1 !== -1) {
1790
- const rest$1 = rawArgv.slice(initIndex$1 + 1);
1791
- const doubleDashIndex = rest$1.indexOf("--");
1792
- if (doubleDashIndex !== -1) forwarded$1.push(...rest$1.slice(doubleDashIndex));
1882
+ const initIndex = rawArgv.findIndex((arg) => arg === "init");
1883
+ if (initIndex !== -1) {
1884
+ const rest = rawArgv.slice(initIndex + 1);
1885
+ const doubleDashIndex = rest.indexOf("--");
1886
+ if (doubleDashIndex !== -1) forwarded.push(...rest.slice(doubleDashIndex));
1793
1887
  }
1794
- return forwarded$1;
1888
+ return forwarded;
1795
1889
  }
1796
1890
  const initIndex = rawArgv.findIndex((arg) => arg === "init");
1797
1891
  if (initIndex === -1) return [];
1798
1892
  const rest = rawArgv.slice(initIndex + 1);
1799
1893
  const forwarded = [];
1800
- const filteredFlags = new Set([
1894
+ const filteredFlags = /* @__PURE__ */ new Set([
1801
1895
  "-v",
1802
1896
  "--verbose",
1803
1897
  "-t",
@@ -1848,7 +1942,7 @@ async function runInit(options) {
1848
1942
  const designConfig = resolveDesignSystemConfig(options);
1849
1943
  let newProjectTemplate;
1850
1944
  if (!options.skipPreflight) {
1851
- if ((await preFlightInit(options)).errors[MISSING_DIR_OR_EMPTY_PROJECT]) {
1945
+ if ((await preFlightInit(options)).errors["1"]) {
1852
1946
  const { projectPath, template } = await createProject(options);
1853
1947
  if (!projectPath) process.exit(1);
1854
1948
  options.cwd = projectPath;
@@ -1962,13 +2056,14 @@ function resolveAliasPathUsingTsConfig(inputPath, projectRoot) {
1962
2056
  const paths = cfg.compilerOptions.paths || {};
1963
2057
  for (const [key, values] of Object.entries(paths)) {
1964
2058
  const pattern = key.replace(/\*/g, "(.*)");
1965
- const re = /* @__PURE__ */ new RegExp(`^${pattern}$`);
2059
+ const re = new RegExp(`^${pattern}$`);
1966
2060
  const match = inputPath.match(re);
1967
2061
  if (!match) continue;
1968
2062
  const wildcard = match[1] || "";
1969
2063
  const first = Array.isArray(values) ? values[0] : values;
1970
2064
  if (!first) continue;
1971
- return resolve(projectRoot, baseUrl, String(first).replace(/\*/g, wildcard));
2065
+ const target = String(first).replace(/\*/g, wildcard);
2066
+ return resolve(projectRoot, baseUrl, target);
1972
2067
  }
1973
2068
  return null;
1974
2069
  }
@@ -1979,6 +2074,8 @@ async function generateDocs({ cwd, outDir, verbose }) {
1979
2074
  const DEBUG = process.env.BEJAMAS_DEBUG === "1" || process.env.BEJAMAS_DEBUG === "true" || verbose;
1980
2075
  try {
1981
2076
  const shellCwd = process.cwd();
2077
+ if (cwd && cwd.length) process.env.BEJAMAS_UI_ROOT = resolve(shellCwd, cwd);
2078
+ if (outDir && outDir.length) process.env.BEJAMAS_DOCS_OUT_DIR = outDir;
1982
2079
  let projectRoot = shellCwd;
1983
2080
  let probe = shellCwd;
1984
2081
  for (let i = 0; i < 6 && probe; i += 1) {
@@ -2030,27 +2127,30 @@ async function generateDocs({ cwd, outDir, verbose }) {
2030
2127
  }
2031
2128
  if (!process.env.BEJAMAS_DOCS_CWD) process.env.BEJAMAS_DOCS_CWD = shellCwd;
2032
2129
  if (!process.env.BEJAMAS_UI_ROOT) {
2130
+ const defaultGuess = (() => {
2131
+ let current = shellCwd;
2132
+ for (let i = 0; i < 6; i += 1) {
2133
+ const cand = resolve(current, "packages/ui/package.json");
2134
+ if (existsSync(cand)) {
2135
+ const abs = resolve(current, "packages/ui");
2136
+ return relative(shellCwd, abs) || abs;
2137
+ }
2138
+ const parent = resolve(current, "..");
2139
+ if (parent === current) break;
2140
+ current = parent;
2141
+ }
2142
+ const nm = resolve(shellCwd, "node_modules/@bejamas/ui/package.json");
2143
+ if (existsSync(nm)) {
2144
+ const abs = resolve(shellCwd, "node_modules/@bejamas/ui");
2145
+ return relative(shellCwd, abs) || abs;
2146
+ }
2147
+ return "packages/ui";
2148
+ })();
2033
2149
  const { uiRoot } = await prompts({
2034
2150
  type: "text",
2035
2151
  name: "uiRoot",
2036
2152
  message: "Path to @bejamas/ui package root:",
2037
- initial: (() => {
2038
- let current = shellCwd;
2039
- for (let i = 0; i < 6; i += 1) {
2040
- if (existsSync(resolve(current, "packages/ui/package.json"))) {
2041
- const abs = resolve(current, "packages/ui");
2042
- return relative(shellCwd, abs) || abs;
2043
- }
2044
- const parent = resolve(current, "..");
2045
- if (parent === current) break;
2046
- current = parent;
2047
- }
2048
- if (existsSync(resolve(shellCwd, "node_modules/@bejamas/ui/package.json"))) {
2049
- const abs = resolve(shellCwd, "node_modules/@bejamas/ui");
2050
- return relative(shellCwd, abs) || abs;
2051
- }
2052
- return "packages/ui";
2053
- })(),
2153
+ initial: defaultGuess,
2054
2154
  validate: (val) => {
2055
2155
  const p = resolve(shellCwd, val);
2056
2156
  return existsSync(resolve(p, "package.json")) ? true : `No package.json found in ${p}`;
@@ -2062,8 +2162,6 @@ async function generateDocs({ cwd, outDir, verbose }) {
2062
2162
  }
2063
2163
  process.env.BEJAMAS_UI_ROOT = resolve(shellCwd, uiRoot);
2064
2164
  }
2065
- if (cwd && cwd.length) process.env.BEJAMAS_UI_ROOT = resolve(cwd);
2066
- if (outDir && outDir.length) process.env.BEJAMAS_DOCS_OUT_DIR = outDir;
2067
2165
  if (!process.env.BEJAMAS_DOCS_OUT_DIR) {
2068
2166
  const { out } = await prompts({
2069
2167
  type: "text",
@@ -2086,7 +2184,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
2086
2184
  if (process.env.BEJAMAS_DOCS_CWD) logger.info(`Docs CWD: ${process.env.BEJAMAS_DOCS_CWD}`);
2087
2185
  if (process.env.BEJAMAS_DOCS_OUT_DIR) logger.info(`Docs out: ${process.env.BEJAMAS_DOCS_OUT_DIR}`);
2088
2186
  }
2089
- const mod = await import("./generate-mdx-Ce7A9Uxe.js");
2187
+ const mod = await import("./generate-mdx-Bd_qGcMg.js");
2090
2188
  if (typeof mod.runDocsGenerator === "function") await mod.runDocsGenerator();
2091
2189
  else throw new Error("Failed to load docs generator. Export 'runDocsGenerator' not found.");
2092
2190
  } catch (err) {
@@ -2123,7 +2221,9 @@ const FIELD_LABELS = {
2123
2221
  figmaUrl: "@figmaUrl"
2124
2222
  };
2125
2223
  function checkComponentDocs(filePath, fileName) {
2126
- const meta = parseJsDocMetadata(extractFrontmatter(readFileSync(filePath, "utf-8")));
2224
+ const content = readFileSync(filePath, "utf-8");
2225
+ const frontmatter = extractFrontmatter(content);
2226
+ const meta = parseJsDocMetadata(frontmatter);
2127
2227
  const componentName = fileName.replace(/\.astro$/i, "");
2128
2228
  const missingRequired = [];
2129
2229
  const missingRecommended = [];
@@ -2271,33 +2371,286 @@ const docsCheck = new Command().name("docs:check").description("check documentat
2271
2371
  });
2272
2372
  });
2273
2373
 
2374
+ //#endregion
2375
+ //#region ../registry/src/lib/headless-dependencies.ts
2376
+ /** Headless behaviors imported by emitted frontmatter, client scripts or helpers. */
2377
+ function getHeadlessDependencies(files) {
2378
+ const dependencies = /* @__PURE__ */ new Set();
2379
+ for (const file of files) {
2380
+ const source = (file.content ?? "").replace(/\/\*[\s\S]*?\*\//g, "");
2381
+ for (const match of source.matchAll(/(?:\bfrom\s*|\bimport\s*\(?\s*)["'](@data-slot\/[^/"']+)(?:\/[^"']*)?["']/g)) dependencies.add(match[1]);
2382
+ }
2383
+ return [...dependencies].sort();
2384
+ }
2385
+
2386
+ //#endregion
2387
+ //#region src/utils/registry-dependencies.ts
2388
+ function missingRegistryDependencies(manifest, items) {
2389
+ const declared = {
2390
+ ...manifest.devDependencies,
2391
+ ...manifest.dependencies
2392
+ };
2393
+ const candidates = /* @__PURE__ */ new Map();
2394
+ for (const item of items) for (const specifier of item.dependencies ?? []) {
2395
+ const name = specifier.match(/^(@[^/]+\/[^@/]+|[^@/]+)/)?.[1];
2396
+ if (name && !declared[name]) candidates.set(name, specifier);
2397
+ }
2398
+ for (const item of items) for (const name of getHeadlessDependencies(item.files ?? [])) if (!declared[name] && !candidates.has(name)) candidates.set(name, name);
2399
+ return [...candidates.values()].sort();
2400
+ }
2401
+ async function ensureRegistryDependencies(cwd, items) {
2402
+ const missing = missingRegistryDependencies(JSON.parse(await fs$1.readFile(path.join(cwd, "package.json"), "utf8")), items);
2403
+ if (!missing.length) return;
2404
+ const manager = await getPackageManager(cwd);
2405
+ await execa(manager, [manager === "npm" ? "install" : "add", ...missing], {
2406
+ cwd,
2407
+ stdio: "inherit"
2408
+ });
2409
+ }
2410
+
2411
+ //#endregion
2412
+ //#region src/utils/registry-install.ts
2413
+ /**
2414
+ * shadcn reports written files relative to the directory it ran in, except in
2415
+ * a workspace where paths are relative to the monorepo root. Resolve each
2416
+ * reported path against the candidate bases and keep the ones that exist; the
2417
+ * first base with a match wins, so list the one shadcn reports against first.
2418
+ */
2419
+ function resolveReportedFiles(files, bases, exists = existsSync) {
2420
+ const resolved = /* @__PURE__ */ new Set();
2421
+ for (const file of files) {
2422
+ const match = (path.isAbsolute(file) ? [file] : bases.map((base) => path.resolve(base, file))).find((candidate) => exists(candidate));
2423
+ if (match) resolved.add(match);
2424
+ }
2425
+ return Array.from(resolved);
2426
+ }
2427
+ /**
2428
+ * Bases for `resolveReportedFiles`. In a workspace, shadcn reports paths
2429
+ * relative to the monorepo root, so it comes before the app directory.
2430
+ */
2431
+ function getReportedFileBases(cwd, config, uiConfig) {
2432
+ const workspaceRoot = config && uiConfig && uiConfig.resolvedPaths.cwd !== config.resolvedPaths.cwd ? findCommonRoot(config.resolvedPaths.cwd, uiConfig.resolvedPaths.cwd) : null;
2433
+ return workspaceRoot ? [workspaceRoot, cwd] : [cwd];
2434
+ }
2435
+ /** Installed files (relative to cwd or absolute) not covered by config aliases. */
2436
+ function filesOutsideConfigRoots(cwd, files, config) {
2437
+ const roots = getConfiguredSourceRoots(config);
2438
+ return files.filter((file) => !roots.some((root) => isPathWithin(path.resolve(cwd, file), root)));
2439
+ }
2440
+ function buildSubfolderMap(registryItems) {
2441
+ const filenameToSubfolders = /* @__PURE__ */ new Map();
2442
+ for (const registryItem of registryItems) {
2443
+ const subfolder = getSubfolderFromPaths(registryItem.files);
2444
+ if (!subfolder) continue;
2445
+ for (const file of registryItem.files ?? []) if (file.type === "registry:ui") {
2446
+ const filename = path.basename(file.path);
2447
+ const subfolders = filenameToSubfolders.get(filename) || [];
2448
+ subfolders.push(subfolder);
2449
+ filenameToSubfolders.set(filename, subfolders);
2450
+ }
2451
+ }
2452
+ const uniqueMap = /* @__PURE__ */ new Map();
2453
+ const sharedFilenames = /* @__PURE__ */ new Set();
2454
+ filenameToSubfolders.forEach((subfolders, filename) => {
2455
+ if (subfolders.length === 1) uniqueMap.set(filename, `${subfolders[0]}/${filename}`);
2456
+ else sharedFilenames.add(filename);
2457
+ });
2458
+ return {
2459
+ uniqueMap,
2460
+ sharedFilenames
2461
+ };
2462
+ }
2463
+ /**
2464
+ * Repairs the output of several shadcn runs in one command. Work the next run
2465
+ * depends on happens per item in `install`; project-wide passes (import scan,
2466
+ * package exports, dependency installs) run once in `finish`, which the command
2467
+ * also calls before exiting on a failure so completed items stay usable.
2468
+ */
2469
+ var RegistryInstallBatch = class {
2470
+ constructor(context) {
2471
+ this.context = context;
2472
+ this.items = [];
2473
+ this.written = /* @__PURE__ */ new Set();
2474
+ this.kept = /* @__PURE__ */ new Set();
2475
+ this.bases = getReportedFileBases(context.cwd, context.config, context.uiConfig);
2476
+ }
2477
+ async install({ items, writtenFiles, skippedFiles }) {
2478
+ const { cwd, uiDir, verbose, overwrite } = this.context;
2479
+ const reorganization = await reorganizeRegistryItems(items, uiDir, verbose, overwrite);
2480
+ this.items.push(...items);
2481
+ for (const file of resolveReportedFiles(writtenFiles, this.bases)) this.written.add(file);
2482
+ for (const file of resolveReportedFiles(skippedFiles, this.bases)) this.kept.add(file);
2483
+ for (const file of reorganization.skippedFiles) this.kept.add(path.resolve(uiDir, file));
2484
+ await syncManagedTailwindCss(cwd);
2485
+ const requestedItem = items.at(-1);
2486
+ if (requestedItem?.type === "registry:font") {
2487
+ const nextFont = toManagedAstroFont(requestedItem.name);
2488
+ if (nextFont) {
2489
+ const currentFonts = await readManagedAstroFontsFromProject(cwd);
2490
+ const nextFonts = mergeManagedAstroFonts(currentFonts, nextFont);
2491
+ await syncAstroFontsInProject(cwd, nextFonts, nextFont.cssVariable);
2492
+ await syncAstroManagedFontCss(cwd, nextFont.cssVariable);
2493
+ await cleanupAstroFontPackages(cwd);
2494
+ }
2495
+ }
2496
+ return reorganization;
2497
+ }
2498
+ async finish() {
2499
+ if (!this.items.length) return;
2500
+ const { cwd, config, uiConfig, uiDir, verbose } = this.context;
2501
+ const items = this.items.splice(0);
2502
+ const written = Array.from(this.written);
2503
+ const kept = Array.from(this.kept).filter((file) => !this.written.has(file));
2504
+ this.written.clear();
2505
+ this.kept.clear();
2506
+ await fixAstroImports(cwd, verbose, uiConfig, {
2507
+ kind: "configured-roots",
2508
+ exclude: kept
2509
+ });
2510
+ if (config) {
2511
+ const appFiles = uiConfig ? filesOutsideConfigRoots(cwd, written, uiConfig) : written;
2512
+ if (appFiles.length) await fixAstroImports(cwd, verbose, config, {
2513
+ kind: "files",
2514
+ paths: appFiles
2515
+ });
2516
+ }
2517
+ if (uiConfig) {
2518
+ await repairUiPackageExports(uiDir, uiConfig.resolvedPaths.cwd);
2519
+ if (!config || config.resolvedPaths.cwd === uiConfig.resolvedPaths.cwd) await ensureRegistryDependencies(uiConfig.resolvedPaths.cwd, items);
2520
+ else {
2521
+ await ensureRegistryDependencies(uiConfig.resolvedPaths.cwd, items.filter((item) => item.type !== "registry:block"));
2522
+ const blockItems = items.filter((item) => item.type === "registry:block");
2523
+ if (blockItems.length) await ensureRegistryDependencies(config.resolvedPaths.cwd, blockItems);
2524
+ }
2525
+ }
2526
+ }
2527
+ };
2528
+
2529
+ //#endregion
2530
+ //#region src/utils/shadcn-prompts.ts
2531
+ const QUESTION_PATTERN = /(?:^|\s)\?\s+([^\r\n]+?)\s+›/g;
2532
+ const CONFIRM_PATTERN = /(?:^|\s)\?\s+([^\r\n]+?)\s+›\s*\((?:y\/N|Y\/n)\)/g;
2533
+ const SUMMARY_PATTERN = /(?:Created|Updated|Skipped)\s+\d+\s+file|No files updated/i;
2534
+ /**
2535
+ * Decline every confirm question (overwrites, setup offers), then close stdin
2536
+ * when shadcn finishes its file phase. Any other question is reported through
2537
+ * `onUnsupportedPrompt` instead of being left to hang.
2538
+ */
2539
+ function respondToShadcnPrompts(subprocess, { onUnsupportedPrompt, unsupportedPromptDelayMs = 250 } = {}) {
2540
+ let unsupportedTimer;
2541
+ let waitingOn;
2542
+ let reported = false;
2543
+ function clearUnsupportedTimer() {
2544
+ if (unsupportedTimer) clearTimeout(unsupportedTimer);
2545
+ unsupportedTimer = void 0;
2546
+ waitingOn = void 0;
2547
+ }
2548
+ function inspect(line, answered) {
2549
+ const text = stripVTControlCharacters(line);
2550
+ const stdin = subprocess.stdin;
2551
+ const canAnswer = stdin && !stdin.destroyed && !stdin.writableEnded;
2552
+ if (canAnswer) for (const match of text.matchAll(CONFIRM_PATTERN)) {
2553
+ const message = match[1];
2554
+ if (answered.has(message)) continue;
2555
+ answered.add(message);
2556
+ stdin.write("n\n");
2557
+ if (message === waitingOn) clearUnsupportedTimer();
2558
+ }
2559
+ const message = Array.from(text.matchAll(QUESTION_PATTERN), (match) => match[1]).filter((message) => !answered.has(message)).at(-1);
2560
+ if (message && message !== waitingOn && onUnsupportedPrompt && !reported) {
2561
+ clearUnsupportedTimer();
2562
+ waitingOn = message;
2563
+ unsupportedTimer = setTimeout(() => {
2564
+ reported = true;
2565
+ onUnsupportedPrompt(message);
2566
+ }, unsupportedPromptDelayMs);
2567
+ unsupportedTimer.unref?.();
2568
+ }
2569
+ if (canAnswer && SUMMARY_PATTERN.test(text)) stdin.end();
2570
+ }
2571
+ for (const stream of [subprocess.stdout, subprocess.stderr]) {
2572
+ const decoder = new StringDecoder("utf8");
2573
+ const answered = /* @__PURE__ */ new Set();
2574
+ let pending = "";
2575
+ stream?.on("data", (chunk) => {
2576
+ pending += typeof chunk === "string" ? chunk : decoder.write(chunk);
2577
+ const lines = pending.split("\n");
2578
+ pending = lines.pop();
2579
+ for (const line of lines) {
2580
+ inspect(line, answered);
2581
+ answered.clear();
2582
+ }
2583
+ inspect(pending, answered);
2584
+ });
2585
+ stream?.on("end", clearUnsupportedTimer);
2586
+ }
2587
+ }
2588
+
2274
2589
  //#endregion
2275
2590
  //#region src/commands/add.ts
2591
+ /** shadcn failed; the output has already been reported. */
2592
+ var InstallFailure = class extends Error {
2593
+ constructor(exitCode) {
2594
+ super(`shadcn exited with code ${exitCode}`);
2595
+ this.exitCode = exitCode;
2596
+ }
2597
+ };
2598
+ function isUiRegistryItem(item) {
2599
+ return !item.type || item.type === "registry:ui";
2600
+ }
2601
+ function isBlockRegistryItem(item) {
2602
+ return item?.type === "registry:block";
2603
+ }
2604
+ /** Items a user can pick by name: UI components and blocks. */
2605
+ function isAddableRegistryItem(item) {
2606
+ return isUiRegistryItem(item) || isBlockRegistryItem(item);
2607
+ }
2608
+ /** `--all` is expanded locally, so shadcn must not expand it a second time. */
2609
+ function withoutExpandedAllOption(forwardedOptions) {
2610
+ return forwardedOptions.filter((option) => option !== "--all" && option !== "-a");
2611
+ }
2612
+ /**
2613
+ * shadcn output is captured and re-reported by this command, and silent mode
2614
+ * would hide the file list needed for post-install repair.
2615
+ */
2616
+ function withoutShadcnSilentOption(forwardedOptions) {
2617
+ return forwardedOptions.filter((option) => option !== "--silent" && option !== "-s");
2618
+ }
2619
+ /**
2620
+ * shadcn only knows the registries declared in components.json. The built-in
2621
+ * `@bejamas/<item>` namespace is the default registry (REGISTRY_URL), so it is
2622
+ * passed to shadcn as the bare item name, which resolves to the styled payload.
2623
+ * Bare names, URLs, and third-party namespaces are passed through unchanged.
2624
+ */
2625
+ function toShadcnAddArgument(item) {
2626
+ if (!item.startsWith("@bejamas/")) return item;
2627
+ return resolveBejamasRegistryItemName(item) ?? item;
2628
+ }
2276
2629
  function extractOptionsForShadcn(rawArgv, cmd) {
2277
2630
  if (typeof cmd.getOptionValueSource === "function") {
2278
2631
  const opts = cmd.optsWithGlobals();
2279
- const forwarded$1 = [];
2632
+ const forwarded = [];
2280
2633
  const getSource = (key) => cmd.getOptionValueSource(key);
2281
2634
  const addBoolean = (key, flag, negateFlag) => {
2282
2635
  if (getSource(key) !== "cli") return;
2283
2636
  const value = opts[key];
2284
2637
  if (typeof value !== "boolean") return;
2285
- if (value) forwarded$1.push(flag);
2286
- else if (negateFlag) forwarded$1.push(negateFlag);
2638
+ if (value) forwarded.push(flag);
2639
+ else if (negateFlag) forwarded.push(negateFlag);
2287
2640
  };
2288
2641
  const addString = (key, flag) => {
2289
2642
  if (getSource(key) !== "cli") return;
2290
2643
  const value = opts[key];
2291
- if (typeof value === "string") forwarded$1.push(flag, value);
2644
+ if (typeof value === "string") forwarded.push(flag, value);
2292
2645
  };
2293
2646
  const addOptionalString = (key, flag) => {
2294
2647
  if (getSource(key) !== "cli") return;
2295
2648
  const value = opts[key];
2296
2649
  if (value === true) {
2297
- forwarded$1.push(flag);
2650
+ forwarded.push(flag);
2298
2651
  return;
2299
2652
  }
2300
- if (typeof value === "string") forwarded$1.push(flag, value);
2653
+ if (typeof value === "string") forwarded.push(flag, value);
2301
2654
  };
2302
2655
  addBoolean("yes", "--yes");
2303
2656
  addBoolean("overwrite", "--overwrite");
@@ -2309,19 +2662,19 @@ function extractOptionsForShadcn(rawArgv, cmd) {
2309
2662
  addBoolean("srcDir", "--src-dir", "--no-src-dir");
2310
2663
  addOptionalString("diff", "--diff");
2311
2664
  addOptionalString("view", "--view");
2312
- const addIndex$1 = rawArgv.findIndex((arg) => arg === "add");
2313
- if (addIndex$1 !== -1) {
2314
- const rest$1 = rawArgv.slice(addIndex$1 + 1);
2315
- const doubleDashIndex = rest$1.indexOf("--");
2316
- if (doubleDashIndex !== -1) forwarded$1.push(...rest$1.slice(doubleDashIndex));
2665
+ const addIndex = rawArgv.findIndex((arg) => arg === "add");
2666
+ if (addIndex !== -1) {
2667
+ const rest = rawArgv.slice(addIndex + 1);
2668
+ const doubleDashIndex = rest.indexOf("--");
2669
+ if (doubleDashIndex !== -1) forwarded.push(...rest.slice(doubleDashIndex));
2317
2670
  }
2318
- return forwarded$1;
2671
+ return forwarded;
2319
2672
  }
2320
2673
  const addIndex = rawArgv.findIndex((arg) => arg === "add");
2321
2674
  if (addIndex === -1) return [];
2322
2675
  const rest = rawArgv.slice(addIndex + 1);
2323
2676
  const forwarded = [];
2324
- const optionsWithValues = new Set([
2677
+ const optionsWithValues = /* @__PURE__ */ new Set([
2325
2678
  "-c",
2326
2679
  "--cwd",
2327
2680
  "-p",
@@ -2329,7 +2682,7 @@ function extractOptionsForShadcn(rawArgv, cmd) {
2329
2682
  "--diff",
2330
2683
  "--view"
2331
2684
  ]);
2332
- const filteredFlags = new Set(["-v", "--verbose"]);
2685
+ const filteredFlags = /* @__PURE__ */ new Set(["-v", "--verbose"]);
2333
2686
  for (let index = 0; index < rest.length; index += 1) {
2334
2687
  const token = rest[index];
2335
2688
  if (token === "--") {
@@ -2365,34 +2718,6 @@ function writeCapturedShadcnOutput(stdout, stderr) {
2365
2718
  if (stdout) process.stdout.write(ensureTrailingNewline(stdout));
2366
2719
  if (stderr) process.stderr.write(ensureTrailingNewline(stderr));
2367
2720
  }
2368
- async function buildSubfolderMap(components, uiDir, registryUrl, style) {
2369
- const filenameToSubfolders = /* @__PURE__ */ new Map();
2370
- let requiresReorganization = false;
2371
- for (const componentName of components) {
2372
- const registryItem = await fetchRegistryItem(componentName, registryUrl, style);
2373
- if (!registryItem) continue;
2374
- if (shouldReorganizeRegistryUiFiles(registryItem.files, uiDir)) requiresReorganization = true;
2375
- const subfolder = getSubfolderFromPaths(registryItem.files);
2376
- if (!subfolder) continue;
2377
- for (const file of registryItem.files) if (file.type === "registry:ui") {
2378
- const filename = path.basename(file.path);
2379
- const subfolders = filenameToSubfolders.get(filename) || [];
2380
- subfolders.push(subfolder);
2381
- filenameToSubfolders.set(filename, subfolders);
2382
- }
2383
- }
2384
- const uniqueMap = /* @__PURE__ */ new Map();
2385
- const sharedFilenames = /* @__PURE__ */ new Set();
2386
- filenameToSubfolders.forEach((subfolders, filename) => {
2387
- if (subfolders.length === 1) uniqueMap.set(filename, `${subfolders[0]}/${filename}`);
2388
- else sharedFilenames.add(filename);
2389
- });
2390
- return {
2391
- uniqueMap,
2392
- sharedFilenames,
2393
- requiresReorganization
2394
- };
2395
- }
2396
2721
  function rewritePaths(paths, mapResult) {
2397
2722
  const { uniqueMap, sharedFilenames } = mapResult;
2398
2723
  let currentSubfolder = null;
@@ -2434,14 +2759,16 @@ async function promptForComponents(registryUrl) {
2434
2759
  logger.warn("No components available in registry.");
2435
2760
  return null;
2436
2761
  }
2762
+ const choices = components.filter(isAddableRegistryItem).map((component) => ({
2763
+ title: component.name,
2764
+ value: component.name,
2765
+ description: isBlockRegistryItem(component) ? "block" : void 0
2766
+ }));
2437
2767
  const { selected } = await prompts({
2438
2768
  type: "autocompleteMultiselect",
2439
2769
  name: "selected",
2440
- message: "Which components would you like to add?",
2441
- choices: components.filter((component) => !component.type || component.type === "registry:ui").map((component) => ({
2442
- title: component.name,
2443
- value: component.name
2444
- })),
2770
+ message: "Which components or blocks would you like to add?",
2771
+ choices,
2445
2772
  hint: "- Space to select. Return to submit.",
2446
2773
  instructions: false
2447
2774
  });
@@ -2454,11 +2781,12 @@ function parseShadcnOutput(stdout, stderr) {
2454
2781
  updated: [],
2455
2782
  skipped: []
2456
2783
  };
2457
- const cleanStderr = stderr.replace(/\x1b\[[0-9;]*m/g, "");
2458
- const cleanStdout = stdout.replace(/\x1b\[[0-9;]*m/g, "");
2459
- const createdMatch = cleanStderr.match(/Created\s+(\d+)\s+file/i);
2460
- const updatedMatch = cleanStderr.match(/Updated\s+(\d+)\s+file/i);
2461
- const skippedMatch = cleanStderr.match(/Skipped\s+(\d+)\s+file/i);
2784
+ const cleanStderr = stripVTControlCharacters(stderr);
2785
+ const cleanStdout = stripVTControlCharacters(stdout);
2786
+ const cleanOutput = `${cleanStdout}\n${cleanStderr}`;
2787
+ const createdMatch = cleanOutput.match(/Created\s+(\d+)\s+file/i);
2788
+ const updatedMatch = cleanOutput.match(/Updated\s+(\d+)\s+file/i);
2789
+ const skippedMatch = cleanOutput.match(/Skipped\s+(\d+)\s+file/i);
2462
2790
  const createdCount = createdMatch ? parseInt(createdMatch[1], 10) : 0;
2463
2791
  const updatedCount = updatedMatch ? parseInt(updatedMatch[1], 10) : 0;
2464
2792
  const skippedCount = skippedMatch ? parseInt(skippedMatch[1], 10) : 0;
@@ -2495,19 +2823,32 @@ async function addComponents(cwd, packages, forwardedOptions, isVerbose, isSilen
2495
2823
  REGISTRY_URL: resolveRegistryUrl()
2496
2824
  };
2497
2825
  await ensurePinnedShadcnExecPrefix();
2498
- const invocation = buildPinnedShadcnInvocation(buildShadcnAddArgs(packages, forwardedOptions));
2826
+ const shadcnArgs = buildShadcnAddArgs(packages, forwardedOptions);
2827
+ const invocation = buildPinnedShadcnInvocation(shadcnArgs);
2499
2828
  if (isVerbose) logger.info(`[bejamas-ui] ${invocation.cmd} ${invocation.args.join(" ")}`);
2500
2829
  const registrySpinner = spinner("Checking registry.", { silent: isSilent });
2501
2830
  registrySpinner.start();
2502
2831
  try {
2503
- const result = await execa(invocation.cmd, invocation.args, {
2832
+ const subprocess = execa(invocation.cmd, invocation.args, {
2504
2833
  cwd,
2505
2834
  env,
2506
- input: "n\nn\nn\nn\nn\nn\nn\nn\nn\nn\n",
2835
+ stdin: "pipe",
2507
2836
  stdout: "pipe",
2508
2837
  stderr: "pipe",
2509
2838
  reject: false
2510
2839
  });
2840
+ let unsupportedPrompt;
2841
+ respondToShadcnPrompts(subprocess, { onUnsupportedPrompt(message) {
2842
+ unsupportedPrompt = message;
2843
+ subprocess.kill();
2844
+ } });
2845
+ const result = await subprocess;
2846
+ if (unsupportedPrompt) {
2847
+ registrySpinner.fail();
2848
+ logger.error(`shadcn asked a question that bejamas add cannot answer: "${unsupportedPrompt}"`);
2849
+ writeCapturedShadcnOutput(result.stdout || "", result.stderr || "");
2850
+ throw new InstallFailure(1);
2851
+ }
2511
2852
  registrySpinner.succeed();
2512
2853
  spinner("Installing components.", { silent: isSilent }).succeed();
2513
2854
  const stdout = result.stdout || "";
@@ -2524,13 +2865,14 @@ async function addComponents(cwd, packages, forwardedOptions, isVerbose, isSilen
2524
2865
  } : parseShadcnOutput(stdout, stderr);
2525
2866
  if (result.exitCode !== 0) {
2526
2867
  if (!inspectionMode) writeCapturedShadcnOutput(stdout, stderr);
2527
- process.exit(result.exitCode);
2868
+ throw new InstallFailure(result.exitCode ?? 1);
2528
2869
  }
2529
2870
  return parsed;
2530
- } catch {
2871
+ } catch (error) {
2872
+ if (error instanceof InstallFailure) throw error;
2531
2873
  registrySpinner.fail();
2532
2874
  logger.error("Failed to add components");
2533
- process.exit(1);
2875
+ throw new InstallFailure(1);
2534
2876
  }
2535
2877
  }
2536
2878
  function buildShadcnAddArgs(packages, forwardedOptions) {
@@ -2552,13 +2894,13 @@ const add = new Command().name("add").description("Add components via the Bejama
2552
2894
  const overwriteUsed = forwardedOptions.includes("--overwrite") || forwardedOptions.includes("-o");
2553
2895
  const cwd = opts.cwd || process.cwd();
2554
2896
  let componentsToAdd = packages || [];
2555
- const wantsAll = Boolean(opts.all);
2897
+ const expandsAll = Boolean(opts.all) && componentsToAdd.length === 0;
2556
2898
  const isSilent = opts.silent || false;
2557
2899
  const registryUrl = resolveRegistryUrl();
2558
- if (wantsAll && componentsToAdd.length === 0) {
2900
+ if (expandsAll) {
2559
2901
  const fetchingSpinner = spinner("Fetching available components.", { silent: isSilent }).start();
2560
2902
  try {
2561
- componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter((component) => !component.type || component.type === "registry:ui").map((component) => component.name);
2903
+ componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter(isUiRegistryItem).map((component) => component.name);
2562
2904
  fetchingSpinner.succeed();
2563
2905
  } catch {
2564
2906
  fetchingSpinner.fail();
@@ -2588,54 +2930,60 @@ const add = new Command().name("add").description("Add components via the Bejama
2588
2930
  }
2589
2931
  const activeStyle = uiConfig?.style || config?.style || "bejamas-juno";
2590
2932
  const totalComponents = componentsToAdd.length;
2591
- for (let index = 0; index < componentsToAdd.length; index += 1) {
2592
- const component = componentsToAdd[index];
2593
- if (totalComponents > 1 && !isSilent) {
2594
- logger.break();
2595
- logger.info(highlighter.info(`[${index + 1}/${totalComponents}]`) + ` Adding ${highlighter.success(component)}...`);
2596
- }
2597
- const subfolderMapResult = inspectionMode ? {
2598
- uniqueMap: /* @__PURE__ */ new Map(),
2599
- sharedFilenames: /* @__PURE__ */ new Set(),
2600
- requiresReorganization: false
2601
- } : await buildSubfolderMap([component], uiDir, registryUrl, activeStyle);
2602
- const parsed = await addComponents(cwd, [component], forwardedOptions, verbose, isSilent, inspectionMode);
2603
- if (!inspectionMode) {
2604
- await syncManagedTailwindCss(cwd);
2605
- const registryItem = await fetchRegistryItem(component, registryUrl, activeStyle);
2606
- if (registryItem?.type === "registry:font") {
2607
- const nextFont = toManagedAstroFont(registryItem.name);
2608
- if (nextFont) {
2609
- await syncAstroFontsInProject(cwd, mergeManagedAstroFonts(await readManagedAstroFontsFromProject(cwd), nextFont), nextFont.cssVariable);
2610
- await syncAstroManagedFontCss(cwd, nextFont.cssVariable);
2611
- await cleanupAstroFontPackages(cwd);
2612
- }
2613
- }
2614
- }
2615
- let skippedCount = 0;
2616
- if (!inspectionMode && uiDir && subfolderMapResult.requiresReorganization) skippedCount = (await reorganizeComponents([component], uiDir, registryUrl, verbose, activeStyle)).skippedFiles.length;
2617
- if (!isSilent && !inspectionMode) {
2618
- const actuallyCreated = Math.max(0, parsed.created.length - skippedCount);
2619
- if (actuallyCreated > 0) {
2620
- const createdPaths = rewritePaths(parsed.created.slice(0, actuallyCreated), subfolderMapResult);
2621
- logger.success(`Created ${createdPaths.length} file${createdPaths.length > 1 ? "s" : ""}:`);
2622
- for (const file of createdPaths) logger.log(` ${highlighter.info("-")} ${file}`);
2623
- }
2624
- if (parsed.updated.length > 0) {
2625
- const updatedPaths = rewritePaths(Array.from(new Set(parsed.updated)), subfolderMapResult);
2626
- logger.info(`Updated ${updatedPaths.length} file${updatedPaths.length > 1 ? "s" : ""}:`);
2627
- for (const file of updatedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
2933
+ const outputBearingOptions = withoutShadcnSilentOption(forwardedOptions);
2934
+ const addOptions = expandsAll ? withoutExpandedAllOption(outputBearingOptions) : outputBearingOptions;
2935
+ const batch = new RegistryInstallBatch({
2936
+ cwd,
2937
+ config,
2938
+ uiConfig,
2939
+ uiDir,
2940
+ verbose,
2941
+ overwrite: overwriteUsed
2942
+ });
2943
+ try {
2944
+ for (let index = 0; index < componentsToAdd.length; index += 1) {
2945
+ const component = componentsToAdd[index];
2946
+ if (totalComponents > 1 && !isSilent) {
2947
+ logger.break();
2948
+ logger.info(highlighter.info(`[${index + 1}/${totalComponents}]`) + ` Adding ${highlighter.success(component)}...`);
2628
2949
  }
2629
- if (skippedCount > 0) logger.info(`Skipped ${skippedCount} file${skippedCount > 1 ? "s" : ""}: (already exists)`);
2630
- if (parsed.skipped.length > 0) {
2631
- const skippedPaths = rewritePaths(parsed.skipped, subfolderMapResult);
2632
- logger.info(formatSkippedFilesHeading(skippedPaths.length, overwriteUsed));
2633
- for (const file of skippedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
2950
+ const items = inspectionMode ? [] : await fetchRegistryTree([component], registryUrl, activeStyle);
2951
+ const subfolderMapResult = buildSubfolderMap(items);
2952
+ const parsed = await addComponents(cwd, [toShadcnAddArgument(component)], addOptions, verbose, isSilent, inspectionMode);
2953
+ if (inspectionMode) continue;
2954
+ const { skippedFiles } = await batch.install({
2955
+ items,
2956
+ writtenFiles: [...parsed.created, ...parsed.updated],
2957
+ skippedFiles: parsed.skipped
2958
+ });
2959
+ const skippedCount = skippedFiles.length;
2960
+ if (!isSilent) {
2961
+ const actuallyCreated = Math.max(0, parsed.created.length - skippedCount);
2962
+ if (actuallyCreated > 0) {
2963
+ const createdPaths = rewritePaths(parsed.created.slice(0, actuallyCreated), subfolderMapResult);
2964
+ logger.success(`Created ${createdPaths.length} file${createdPaths.length > 1 ? "s" : ""}:`);
2965
+ for (const file of createdPaths) logger.log(` ${highlighter.info("-")} ${file}`);
2966
+ }
2967
+ if (parsed.updated.length > 0) {
2968
+ const updatedPaths = rewritePaths(Array.from(new Set(parsed.updated)), subfolderMapResult);
2969
+ logger.info(`Updated ${updatedPaths.length} file${updatedPaths.length > 1 ? "s" : ""}:`);
2970
+ for (const file of updatedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
2971
+ }
2972
+ if (skippedCount > 0) logger.info(`Skipped ${skippedCount} file${skippedCount > 1 ? "s" : ""}: (already exists)`);
2973
+ if (parsed.skipped.length > 0) {
2974
+ const skippedPaths = rewritePaths(parsed.skipped, subfolderMapResult);
2975
+ logger.info(formatSkippedFilesHeading(skippedPaths.length, overwriteUsed));
2976
+ for (const file of skippedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
2977
+ }
2978
+ if (actuallyCreated === 0 && parsed.updated.length === 0 && skippedCount === 0 && parsed.skipped.length === 0) logger.info("Already up to date.");
2634
2979
  }
2635
- if (actuallyCreated === 0 && parsed.updated.length === 0 && skippedCount === 0 && parsed.skipped.length === 0) logger.info("Already up to date.");
2636
2980
  }
2981
+ } catch (error) {
2982
+ await batch.finish();
2983
+ if (error instanceof InstallFailure) process.exit(error.exitCode);
2984
+ throw error;
2637
2985
  }
2638
- if (!inspectionMode) await fixAstroImports(cwd, verbose);
2986
+ await batch.finish();
2639
2987
  });
2640
2988
 
2641
2989
  //#endregion
@@ -2802,14 +3150,14 @@ const apply = new Command().name("apply").description("apply a preset to an exis
2802
3150
  only: explicitOnly
2803
3151
  });
2804
3152
  const preflight = await preFlightApply(options);
2805
- if (preflight.errors[MISSING_DIR_OR_EMPTY_PROJECT]) {
3153
+ if (preflight.errors["1"]) {
2806
3154
  logger.break();
2807
3155
  logger.error(`The ${highlighter.info("apply")} command only works in an existing project.`);
2808
3156
  logger.error(`Run ${highlighter.info(getInitCommand(rawPreset))} first.`);
2809
3157
  logger.break();
2810
3158
  process.exit(1);
2811
3159
  }
2812
- if (preflight.errors[MISSING_CONFIG]) {
3160
+ if (preflight.errors["3"]) {
2813
3161
  logger.break();
2814
3162
  logger.error(`No ${highlighter.info("components.json")} found at ${highlighter.info(options.cwd)}.`);
2815
3163
  logger.error(`Run ${highlighter.info(getInitCommand(rawPreset))} first.`);
@@ -2890,9 +3238,9 @@ function resolveApplyPreset(options) {
2890
3238
  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
3239
  return flagPreset ?? positionalPreset;
2892
3240
  }
2893
- function getPresetUrlOnly(preset$1) {
2894
- if (!isUrl(preset$1)) return;
2895
- return new URL(preset$1).searchParams.get("only") ?? void 0;
3241
+ function getPresetUrlOnly(preset) {
3242
+ if (!isUrl(preset)) return;
3243
+ return new URL(preset).searchParams.get("only") ?? void 0;
2896
3244
  }
2897
3245
  function resolveApplyOnly(value) {
2898
3246
  if (value === void 0 || value === false) return;
@@ -2922,27 +3270,27 @@ function validateApplyOnlyPreset(options) {
2922
3270
  if (!options.only || options.preset) return;
2923
3271
  throw new ApplyOnlyError(["Missing preset for --only.", "Use: bejamas apply <preset> --only theme,font."].join("\n"));
2924
3272
  }
2925
- function resolveApplyPresetSource(preset$1) {
2926
- if (isPresetCode(preset$1)) {
2927
- validatePresetCode(preset$1);
2928
- return { preset: preset$1 };
3273
+ function resolveApplyPresetSource(preset) {
3274
+ if (isPresetCode(preset)) {
3275
+ validatePresetCode(preset);
3276
+ return { preset };
2929
3277
  }
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.`);
3278
+ if (!isUrl(preset)) throw new ApplyPresetError(`Invalid preset: ${highlighter.info(preset)}. Use a preset code or a Bejamas create/init URL containing a preset code.`);
3279
+ const url = new URL(preset);
3280
+ if (!isSupportedPresetUrl(url)) throw new ApplyPresetError(`Invalid preset URL: ${highlighter.info(preset)}. Use a Bejamas create or init URL containing a preset code.`);
3281
+ const code = url.searchParams.get("preset")?.trim();
3282
+ if (!code) throw new ApplyPresetError(`Invalid preset URL: ${highlighter.info(preset)}. Expected a URL containing a preset query parameter.`);
2935
3283
  validatePresetCode(code);
2936
3284
  return {
2937
3285
  preset: code,
2938
- themeRef: url$1.searchParams.get("themeRef") ?? void 0,
2939
- only: url$1.searchParams.get("only") ?? void 0
3286
+ themeRef: url.searchParams.get("themeRef") ?? void 0,
3287
+ only: url.searchParams.get("only") ?? void 0
2940
3288
  };
2941
3289
  }
2942
3290
  async function preFlightApply(options) {
2943
3291
  const errors = {};
2944
3292
  if (!fs.existsSync(options.cwd) || !fs.existsSync(path.resolve(options.cwd, "package.json"))) {
2945
- errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
3293
+ errors["1"] = true;
2946
3294
  return {
2947
3295
  errors,
2948
3296
  config: null
@@ -2964,7 +3312,7 @@ async function preFlightApply(options) {
2964
3312
  process.exit(1);
2965
3313
  }
2966
3314
  }
2967
- errors[MISSING_CONFIG] = true;
3315
+ errors["3"] = true;
2968
3316
  return {
2969
3317
  errors,
2970
3318
  config: null
@@ -3015,28 +3363,29 @@ async function confirmApply(options) {
3015
3363
  process.exit(1);
3016
3364
  }
3017
3365
  }
3018
- async function runPartialApply(cwd, designConfig, preset$1, only) {
3366
+ async function runPartialApply(cwd, designConfig, preset, only) {
3019
3367
  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 });
3368
+ const initUrl = buildInitUrl(designConfig, preset.themeRef);
3369
+ const themeVars = preset.themeRef ? await fetchInitThemeVars(initUrl) ?? void 0 : void 0;
3370
+ await applyDesignSystemThemeToProject(cwd, designConfig, { themeVars });
3022
3371
  await fixAstroImports(cwd, false);
3023
3372
  }
3024
3373
  if (only.includes("font")) await applyDesignSystemFontToProject(cwd, designConfig);
3025
3374
  }
3026
- function resolveApplyDesignSystemConfig(preset$1, existingConfig) {
3375
+ function resolveApplyDesignSystemConfig(preset, existingConfig) {
3027
3376
  return resolveDesignSystemConfig({
3028
- preset: preset$1.preset,
3377
+ preset: preset.preset,
3029
3378
  template: "astro",
3030
3379
  rtl: existingConfig.rtl ?? false,
3031
3380
  baseColor: void 0,
3032
3381
  lang: void 0
3033
3382
  });
3034
3383
  }
3035
- function validatePresetCode(preset$1) {
3036
- if (!isPresetCode(preset$1) || !decodePreset(preset$1)) throw new ApplyPresetError(`Invalid preset code: ${highlighter.info(preset$1)}.`);
3384
+ function validatePresetCode(preset) {
3385
+ if (!isPresetCode(preset) || !decodePreset(preset)) throw new ApplyPresetError(`Invalid preset code: ${highlighter.info(preset)}.`);
3037
3386
  }
3038
3387
  async function getApplyConfigPaths(config) {
3039
- const configPaths = new Set([path.resolve(config.resolvedPaths.cwd, "components.json")]);
3388
+ const configPaths = /* @__PURE__ */ new Set([path.resolve(config.resolvedPaths.cwd, "components.json")]);
3040
3389
  const workspaceConfig = await getWorkspaceConfig(config);
3041
3390
  if (workspaceConfig) for (const linkedConfig of Object.values(workspaceConfig)) configPaths.add(path.resolve(linkedConfig.resolvedPaths.cwd, "components.json"));
3042
3391
  const existingPaths = [];
@@ -3110,9 +3459,9 @@ async function getApplyWorkspaceConfigs(config) {
3110
3459
  function quoteShellArg(value) {
3111
3460
  return /[^A-Za-z0-9_./:-]/.test(value) ? JSON.stringify(value) : value;
3112
3461
  }
3113
- function getInitCommand(preset$1) {
3114
- if (!preset$1) return "bejamas init";
3115
- return `bejamas init --preset ${quoteShellArg(preset$1)}`;
3462
+ function getInitCommand(preset) {
3463
+ if (!preset) return "bejamas init";
3464
+ return `bejamas init --preset ${quoteShellArg(preset)}`;
3116
3465
  }
3117
3466
  function isUrl(value) {
3118
3467
  try {
@@ -3122,8 +3471,8 @@ function isUrl(value) {
3122
3471
  return false;
3123
3472
  }
3124
3473
  }
3125
- function isSupportedPresetUrl(url$1) {
3126
- const pathname = url$1.pathname.replace(/\/+$/, "");
3474
+ function isSupportedPresetUrl(url) {
3475
+ const pathname = url.pathname.replace(/\/+$/, "");
3127
3476
  return pathname === "/create" || pathname === "/init";
3128
3477
  }
3129
3478
 
@@ -3159,13 +3508,14 @@ const PRESET_ICON_LIBRARY_SET = new Set(PRESET_ICON_LIBRARIES);
3159
3508
  const PRESET_MENU_ACCENT_SET = new Set(PRESET_MENU_ACCENTS);
3160
3509
  const PRESET_MENU_COLOR_SET = new Set(PRESET_MENU_COLORS);
3161
3510
  const PRESET_STYLE_SET = new Set(PRESET_STYLES);
3511
+ new Set(PRESET_THEMES);
3162
3512
  const ROOT_FONT_VARIABLES = [
3163
3513
  "--font-sans",
3164
3514
  "--font-serif",
3165
3515
  "--font-mono"
3166
3516
  ];
3167
3517
  const ROOT_FONT_VARIABLE_SET = new Set(ROOT_FONT_VARIABLES);
3168
- const FONT_VARIABLE_SET = new Set([...ROOT_FONT_VARIABLES, "--font-heading"]);
3518
+ const FONT_VARIABLE_SET = /* @__PURE__ */ new Set([...ROOT_FONT_VARIABLES, "--font-heading"]);
3169
3519
  async function resolveProjectPreset(config) {
3170
3520
  const style = normalizePresetStyle(config.style);
3171
3521
  if (!style) return {
@@ -3463,31 +3813,31 @@ function printPresetDecode(result) {
3463
3813
  values: result.values
3464
3814
  }, { fallbackNote: " * Compatibility value for older preset versions." });
3465
3815
  }
3466
- function printPresetInfo(preset$1, options = {}) {
3816
+ function printPresetInfo(preset, options = {}) {
3467
3817
  logger.log(highlighter.info("Preset"));
3468
- if (!preset$1.code || !preset$1.values) {
3818
+ if (!preset.code || !preset.values) {
3469
3819
  printEntries({ code: "-" });
3470
3820
  return;
3471
3821
  }
3472
- const fallbacks = preset$1.fallbacks ?? [];
3822
+ const fallbacks = preset.fallbacks ?? [];
3473
3823
  const formatPresetValue = (key, value) => {
3474
3824
  const suffix = fallbacks.includes(key) ? "*" : "";
3475
3825
  return `${value ?? "-"}${suffix}`;
3476
3826
  };
3477
3827
  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)
3828
+ code: preset.code,
3829
+ version: preset.code[0],
3830
+ style: preset.values.style,
3831
+ baseColor: formatPresetValue("baseColor", preset.values.baseColor),
3832
+ theme: formatPresetValue("theme", preset.values.theme),
3833
+ chartColor: formatPresetValue("chartColor", preset.values.chartColor),
3834
+ iconLibrary: formatPresetValue("iconLibrary", preset.values.iconLibrary),
3835
+ font: formatPresetValue("font", preset.values.font),
3836
+ fontHeading: formatPresetValue("fontHeading", preset.values.fontHeading),
3837
+ radius: formatPresetValue("radius", preset.values.radius),
3838
+ menuAccent: formatPresetValue("menuAccent", preset.values.menuAccent),
3839
+ menuColor: formatPresetValue("menuColor", preset.values.menuColor),
3840
+ url: getPresetUrl(preset.code)
3491
3841
  });
3492
3842
  if (fallbacks.length > 0) {
3493
3843
  logger.log("");
@@ -3600,8 +3950,7 @@ program.addCommand(docs);
3600
3950
  program.addCommand(docsBuild);
3601
3951
  program.addCommand(docsCheck);
3602
3952
  program.parse(process.argv);
3603
- var src_default = program;
3604
3953
 
3605
3954
  //#endregion
3606
- export { src_default as default };
3955
+ export { program as default };
3607
3956
  //# sourceMappingURL=index.js.map