@metacells/mcellui-cli 0.1.3 → 0.1.5

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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/index.js +567 -273
  3. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -116,8 +116,8 @@ function validateConfig(config) {
116
116
  return { success: true, data: result.data };
117
117
  }
118
118
  const errors = result.error.issues.map((issue) => {
119
- const path10 = issue.path.join(".");
120
- return path10 ? `${path10}: ${issue.message}` : issue.message;
119
+ const path12 = issue.path.join(".");
120
+ return path12 ? `${path12}: ${issue.message}` : issue.message;
121
121
  });
122
122
  return { success: false, errors };
123
123
  }
@@ -212,27 +212,38 @@ async function detectProjectType(projectRoot) {
212
212
  return "unknown";
213
213
  }
214
214
  }
215
+ var CONFIG_FILES = [
216
+ "mcellui.config.ts",
217
+ "mcellui.config.js",
218
+ "mcellui.config.json",
219
+ "nativeui.config.ts",
220
+ // Legacy
221
+ "nativeui.config.js",
222
+ // Legacy
223
+ "nativeui.config.json"
224
+ // Legacy
225
+ ];
215
226
  async function getConfig(projectRoot) {
216
- const tsConfigPath = path.join(projectRoot, "nativeui.config.ts");
217
- if (await fs.pathExists(tsConfigPath)) {
218
- return loadTsConfig(tsConfigPath);
219
- }
220
- const jsConfigPath = path.join(projectRoot, "nativeui.config.js");
221
- if (await fs.pathExists(jsConfigPath)) {
222
- return loadJsConfig(jsConfigPath);
223
- }
224
- const jsonConfigPath = path.join(projectRoot, "nativeui.config.json");
225
- if (await fs.pathExists(jsonConfigPath)) {
226
- try {
227
- const rawConfig = await fs.readJson(jsonConfigPath);
228
- const validatedConfig = validateConfigOrThrow(rawConfig, jsonConfigPath);
229
- return resolveConfig(validatedConfig);
230
- } catch (error) {
231
- if (error instanceof Error && error.message.includes("Invalid configuration")) {
232
- console.error(chalk.red(error.message));
233
- throw error;
227
+ for (const fileName of CONFIG_FILES) {
228
+ const configPath = path.join(projectRoot, fileName);
229
+ if (await fs.pathExists(configPath)) {
230
+ if (fileName.endsWith(".ts")) {
231
+ return loadTsConfig(configPath);
232
+ } else if (fileName.endsWith(".js")) {
233
+ return loadJsConfig(configPath);
234
+ } else if (fileName.endsWith(".json")) {
235
+ try {
236
+ const rawConfig = await fs.readJson(configPath);
237
+ const validatedConfig = validateConfigOrThrow(rawConfig, configPath);
238
+ return resolveConfig(validatedConfig);
239
+ } catch (error) {
240
+ if (error instanceof Error && error.message.includes("Invalid configuration")) {
241
+ console.error(chalk.red(error.message));
242
+ throw error;
243
+ }
244
+ throw error;
245
+ }
234
246
  }
235
- throw error;
236
247
  }
237
248
  }
238
249
  return null;
@@ -294,7 +305,7 @@ async function detectPackageManager(projectRoot) {
294
305
  }
295
306
 
296
307
  // src/commands/init.ts
297
- var initCommand = new Command().name("init").description("Initialize nativeui in your project").option("-y, --yes", "Skip prompts and use defaults").option("--cwd <path>", "Working directory", process.cwd()).action(async (options) => {
308
+ var initCommand = new Command().name("init").description("Initialize mcellui in your project").option("-y, --yes", "Skip prompts and use defaults").option("--cwd <path>", "Working directory", process.cwd()).action(async (options) => {
298
309
  const spinner = ora();
299
310
  try {
300
311
  const cwd = path2.resolve(options.cwd);
@@ -305,17 +316,24 @@ var initCommand = new Command().name("init").description("Initialize nativeui in
305
316
  process.exit(1);
306
317
  }
307
318
  console.log();
308
- console.log(chalk2.bold("Welcome to nativeui!"));
319
+ console.log(chalk2.bold("Welcome to mcellui!"));
309
320
  console.log(chalk2.dim("The copy-paste component library for Expo/React Native"));
310
321
  console.log();
311
322
  const projectType = await detectProjectType(projectRoot);
312
323
  console.log(chalk2.dim(`Detected: ${projectType}`));
313
- const configPath = path2.join(projectRoot, "nativeui.config.ts");
324
+ const configPath = path2.join(projectRoot, "mcellui.config.ts");
325
+ const legacyConfigPath = path2.join(projectRoot, "nativeui.config.ts");
314
326
  if (await fs2.pathExists(configPath)) {
315
327
  console.log(chalk2.yellow("Project already initialized."));
316
328
  console.log(chalk2.dim(`Config found at: ${configPath}`));
317
329
  return;
318
330
  }
331
+ if (await fs2.pathExists(legacyConfigPath)) {
332
+ console.log(chalk2.yellow("Project already initialized with legacy config."));
333
+ console.log(chalk2.dim(`Config found at: ${legacyConfigPath}`));
334
+ console.log(chalk2.dim("Consider renaming to mcellui.config.ts"));
335
+ return;
336
+ }
319
337
  let config = {
320
338
  componentsPath: "./components/ui",
321
339
  utilsPath: "./lib/utils",
@@ -454,14 +472,14 @@ export function cn(...inputs: StyleInput[]): Style {
454
472
  );
455
473
  spinner.succeed("Utilities installed");
456
474
  console.log();
457
- console.log(chalk2.green("Success!") + " nativeui initialized.");
475
+ console.log(chalk2.green("Success!") + " mcellui initialized.");
458
476
  console.log();
459
477
  console.log("Next steps:");
460
478
  console.log(chalk2.dim(" 1."), "Add your first component:");
461
- console.log(chalk2.cyan(" npx nativeui add button"));
479
+ console.log(chalk2.cyan(" npx mcellui add button"));
462
480
  console.log();
463
481
  console.log(chalk2.dim(" 2."), "Browse available components:");
464
- console.log(chalk2.cyan(" npx nativeui list"));
482
+ console.log(chalk2.cyan(" npx mcellui list"));
465
483
  console.log();
466
484
  } catch (error) {
467
485
  spinner.fail("Failed to initialize");
@@ -475,8 +493,8 @@ import { Command as Command2 } from "commander";
475
493
  import chalk3 from "chalk";
476
494
  import ora2 from "ora";
477
495
  import prompts2 from "prompts";
478
- import fs4 from "fs-extra";
479
- import path4 from "path";
496
+ import fs5 from "fs-extra";
497
+ import path5 from "path";
480
498
 
481
499
  // src/utils/registry.ts
482
500
  import fs3 from "fs-extra";
@@ -485,7 +503,8 @@ import { fileURLToPath as fileURLToPath2 } from "url";
485
503
  var __filename3 = fileURLToPath2(import.meta.url);
486
504
  var __dirname2 = path3.dirname(__filename3);
487
505
  var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/metacells-development/mcellui/main/packages/registry";
488
- var REGISTRY_URL = process.env.MCELLUI_REGISTRY_URL || process.env.NATIVEUI_REGISTRY_URL || DEFAULT_REGISTRY_URL;
506
+ var hasEnvOverride = "MCELLUI_REGISTRY_URL" in process.env || "NATIVEUI_REGISTRY_URL" in process.env;
507
+ var REGISTRY_URL = hasEnvOverride ? process.env.MCELLUI_REGISTRY_URL || process.env.NATIVEUI_REGISTRY_URL || "" : DEFAULT_REGISTRY_URL;
489
508
  function getLocalRegistryPath() {
490
509
  return path3.resolve(__dirname2, "..", "..", "registry");
491
510
  }
@@ -567,11 +586,172 @@ async function fetchRemoteComponent(item) {
567
586
  };
568
587
  }
569
588
 
589
+ // src/utils/dependencies.ts
590
+ function resolveDependencies(components, registry) {
591
+ const registryMap = /* @__PURE__ */ new Map();
592
+ for (const item of registry) {
593
+ registryMap.set(item.name, item);
594
+ }
595
+ const resolved = /* @__PURE__ */ new Set();
596
+ const resolving = /* @__PURE__ */ new Set();
597
+ const resolvedOrder = [];
598
+ function resolve(name, path12) {
599
+ if (resolved.has(name)) {
600
+ return null;
601
+ }
602
+ if (resolving.has(name)) {
603
+ return [...path12, name];
604
+ }
605
+ const item = registryMap.get(name);
606
+ if (!item) {
607
+ return null;
608
+ }
609
+ resolving.add(name);
610
+ const deps = item.registryDependencies || [];
611
+ for (const dep of deps) {
612
+ const circular = resolve(dep, [...path12, name]);
613
+ if (circular) {
614
+ return circular;
615
+ }
616
+ }
617
+ resolving.delete(name);
618
+ resolved.add(name);
619
+ resolvedOrder.push(name);
620
+ return null;
621
+ }
622
+ for (const name of components) {
623
+ const circular = resolve(name, []);
624
+ if (circular) {
625
+ return {
626
+ resolved: [],
627
+ circular
628
+ };
629
+ }
630
+ }
631
+ return {
632
+ resolved: resolvedOrder,
633
+ circular: null
634
+ };
635
+ }
636
+ function formatCircularError(chain) {
637
+ return chain.join(" \u2192 ");
638
+ }
639
+
640
+ // src/utils/installed.ts
641
+ import fs4 from "fs-extra";
642
+ import path4 from "path";
643
+
644
+ // src/utils/imports.ts
645
+ function transformToInstalled(code, config) {
646
+ if (!config) return code;
647
+ let result = code;
648
+ const utilsAlias = config.aliases?.utils || "@/lib/utils";
649
+ if (utilsAlias !== "@/lib/utils") {
650
+ result = result.replace(
651
+ /from ['"]@\/lib\/utils['"]/g,
652
+ `from '${utilsAlias}'`
653
+ );
654
+ }
655
+ return result;
656
+ }
657
+ function normalizeForComparison(content) {
658
+ return content.replace(/\r\n/g, "\n").replace(/\s+$/gm, "").trim();
659
+ }
660
+
661
+ // src/utils/installed.ts
662
+ async function getInstalledFiles(componentsDir) {
663
+ if (!await fs4.pathExists(componentsDir)) {
664
+ return [];
665
+ }
666
+ const files = await fs4.readdir(componentsDir);
667
+ const componentFiles = [];
668
+ for (const file of files) {
669
+ if (file.endsWith(".tsx") || file.endsWith(".ts")) {
670
+ if (file === "index.ts" || file === "index.tsx") {
671
+ continue;
672
+ }
673
+ componentFiles.push(path4.join(componentsDir, file));
674
+ }
675
+ }
676
+ return componentFiles.sort();
677
+ }
678
+ async function getInstallStatus(installedFiles, registry, config) {
679
+ const results = [];
680
+ const registryMap = /* @__PURE__ */ new Map();
681
+ for (const item of registry) {
682
+ for (const file of item.files) {
683
+ const fileName = path4.basename(file);
684
+ registryMap.set(fileName, item);
685
+ }
686
+ }
687
+ for (const localFile of installedFiles) {
688
+ const fileName = path4.basename(localFile);
689
+ const componentName = fileName.replace(/\.tsx?$/, "");
690
+ const registryItem = registryMap.get(fileName);
691
+ if (!registryItem) {
692
+ results.push({
693
+ name: componentName,
694
+ fileName,
695
+ status: "local-only",
696
+ localPath: localFile
697
+ });
698
+ continue;
699
+ }
700
+ try {
701
+ const component = await fetchComponent(registryItem.name);
702
+ if (!component) {
703
+ results.push({
704
+ name: registryItem.name,
705
+ fileName,
706
+ status: "modified",
707
+ // Assume modified if we can't verify
708
+ localPath: localFile
709
+ });
710
+ continue;
711
+ }
712
+ const registryFile = component.files.find((f) => f.name === fileName);
713
+ if (!registryFile) {
714
+ results.push({
715
+ name: registryItem.name,
716
+ fileName,
717
+ status: "modified",
718
+ localPath: localFile
719
+ });
720
+ continue;
721
+ }
722
+ const localContent = await fs4.readFile(localFile, "utf-8");
723
+ const normalizedLocal = normalizeForComparison(localContent);
724
+ const normalizedRegistry = normalizeForComparison(registryFile.content);
725
+ results.push({
726
+ name: registryItem.name,
727
+ fileName,
728
+ status: normalizedLocal === normalizedRegistry ? "identical" : "modified",
729
+ localPath: localFile
730
+ });
731
+ } catch {
732
+ results.push({
733
+ name: registryItem.name,
734
+ fileName,
735
+ status: "modified",
736
+ // Assume modified on error
737
+ localPath: localFile
738
+ });
739
+ }
740
+ }
741
+ return results;
742
+ }
743
+ function getInstalledNames(installedFiles) {
744
+ return installedFiles.map((file) => {
745
+ const fileName = path4.basename(file);
746
+ return fileName.replace(/\.tsx?$/, "");
747
+ });
748
+ }
749
+
570
750
  // src/commands/add.ts
571
751
  var addCommand = new Command2().name("add").description("Add a component to your project").argument("[components...]", "Components to add").option("-y, --yes", "Skip confirmation").option("-o, --overwrite", "Overwrite existing files").option("--cwd <path>", "Working directory", process.cwd()).action(async (components, options) => {
572
752
  const spinner = ora2();
573
753
  try {
574
- const cwd = path4.resolve(options.cwd);
754
+ const cwd = path5.resolve(options.cwd);
575
755
  const projectRoot = await getProjectRoot(cwd);
576
756
  if (!projectRoot) {
577
757
  console.log(chalk3.red("Could not find a valid project."));
@@ -585,8 +765,8 @@ var addCommand = new Command2().name("add").description("Add a component to your
585
765
  process.exit(1);
586
766
  }
587
767
  if (!components.length) {
588
- const registry = await getRegistry();
589
- if (!registry.length) {
768
+ const registry2 = await getRegistry();
769
+ if (!registry2.length) {
590
770
  console.log(chalk3.red("No components found in registry."));
591
771
  process.exit(1);
592
772
  }
@@ -594,7 +774,7 @@ var addCommand = new Command2().name("add").description("Add a component to your
594
774
  type: "multiselect",
595
775
  name: "selected",
596
776
  message: "Which components would you like to add?",
597
- choices: registry.map((item) => ({
777
+ choices: registry2.map((item) => ({
598
778
  title: `${item.name} ${chalk3.dim(`(${item.status})`)}`,
599
779
  value: item.name,
600
780
  description: item.description
@@ -607,13 +787,51 @@ var addCommand = new Command2().name("add").description("Add a component to your
607
787
  }
608
788
  components = selected;
609
789
  }
790
+ spinner.start("Resolving dependencies...");
791
+ const registry = await getRegistry();
792
+ const { resolved, circular } = resolveDependencies(components, registry);
793
+ if (circular) {
794
+ spinner.fail("Circular dependency detected");
795
+ console.log(chalk3.red(` ${formatCircularError(circular)}`));
796
+ process.exit(1);
797
+ }
798
+ spinner.stop();
799
+ const componentsDir = path5.join(projectRoot, config.componentsPath);
800
+ const installedFiles = await getInstalledFiles(componentsDir);
801
+ const installedNames = new Set(getInstalledNames(installedFiles));
802
+ const toInstall = options.overwrite ? resolved : resolved.filter((name) => !installedNames.has(name));
803
+ if (toInstall.length === 0) {
804
+ console.log(chalk3.yellow("All components are already installed."));
805
+ console.log(chalk3.dim("Use --overwrite to reinstall."));
806
+ return;
807
+ }
808
+ const requested = new Set(components);
809
+ const dependencies = toInstall.filter((name) => !requested.has(name));
610
810
  console.log();
611
811
  console.log(chalk3.bold("Adding components:"));
612
- components.forEach((c) => console.log(chalk3.dim(` - ${c}`)));
812
+ for (const name of toInstall) {
813
+ if (requested.has(name)) {
814
+ console.log(chalk3.dim(` - ${name}`));
815
+ } else {
816
+ console.log(chalk3.dim(` - ${name} ${chalk3.cyan("(dependency)")}`));
817
+ }
818
+ }
613
819
  console.log();
820
+ if (dependencies.length > 0 && !options.yes) {
821
+ const { confirm } = await prompts2({
822
+ type: "confirm",
823
+ name: "confirm",
824
+ message: `Add ${dependencies.length} additional dependencies?`,
825
+ initial: true
826
+ });
827
+ if (!confirm) {
828
+ console.log(chalk3.dim("Cancelled."));
829
+ return;
830
+ }
831
+ }
614
832
  const allDependencies = [];
615
833
  const allDevDependencies = [];
616
- for (const componentName of components) {
834
+ for (const componentName of toInstall) {
617
835
  spinner.start(`Fetching ${componentName}...`);
618
836
  try {
619
837
  const component = await fetchComponent(componentName);
@@ -621,16 +839,16 @@ var addCommand = new Command2().name("add").description("Add a component to your
621
839
  spinner.fail(`Component "${componentName}" not found`);
622
840
  continue;
623
841
  }
624
- const targetDir = path4.join(projectRoot, config.componentsPath);
842
+ const targetDir = path5.join(projectRoot, config.componentsPath);
625
843
  for (const file of component.files) {
626
- const targetPath = path4.join(targetDir, file.name);
627
- if (await fs4.pathExists(targetPath) && !options.overwrite) {
844
+ const targetPath = path5.join(targetDir, file.name);
845
+ if (await fs5.pathExists(targetPath) && !options.overwrite) {
628
846
  spinner.warn(`${file.name} already exists (use --overwrite)`);
629
847
  continue;
630
848
  }
631
- await fs4.ensureDir(targetDir);
632
- const transformedContent = transformImports(file.content, config);
633
- await fs4.writeFile(targetPath, transformedContent);
849
+ await fs5.ensureDir(targetDir);
850
+ const transformedContent = transformToInstalled(file.content, config);
851
+ await fs5.writeFile(targetPath, transformedContent);
634
852
  }
635
853
  spinner.succeed(`Added ${componentName}`);
636
854
  if (component.dependencies?.length) {
@@ -639,11 +857,6 @@ var addCommand = new Command2().name("add").description("Add a component to your
639
857
  if (component.devDependencies?.length) {
640
858
  allDevDependencies.push(...component.devDependencies);
641
859
  }
642
- if (component.registryDependencies?.length) {
643
- console.log(
644
- chalk3.dim(` Requires: ${component.registryDependencies.join(", ")}`)
645
- );
646
- }
647
860
  } catch (error) {
648
861
  spinner.fail(`Failed to add ${componentName}`);
649
862
  console.error(chalk3.dim(String(error)));
@@ -669,24 +882,20 @@ var addCommand = new Command2().name("add").description("Add a component to your
669
882
  process.exit(1);
670
883
  }
671
884
  });
672
- function transformImports(code, config) {
673
- let transformed = code;
674
- const utilsAlias = config.aliases?.utils || "@/lib/utils";
675
- if (utilsAlias === "@/lib/utils") {
676
- return transformed;
677
- }
678
- transformed = transformed.replace(
679
- /from ['"]@\/lib\/utils['"]/g,
680
- `from '${utilsAlias}'`
681
- );
682
- return transformed;
683
- }
684
885
 
685
886
  // src/commands/list.ts
686
887
  import { Command as Command3 } from "commander";
687
888
  import chalk4 from "chalk";
688
889
  import ora3 from "ora";
689
- var listCommand = new Command3().name("list").description("List available components").option("-c, --category <category>", "Filter by category").action(async (options) => {
890
+ import path6 from "path";
891
+ var listCommand = new Command3().name("list").description("List available or installed components").option("-c, --category <category>", "Filter by category").option("-i, --installed", "Show installed components and their sync status").option("--cwd <path>", "Working directory", process.cwd()).action(async (options) => {
892
+ if (options.installed) {
893
+ await listInstalledComponents(options);
894
+ } else {
895
+ await listAvailableComponents(options);
896
+ }
897
+ });
898
+ async function listAvailableComponents(options) {
690
899
  const spinner = ora3("Fetching components...").start();
691
900
  try {
692
901
  const registry = await getRegistry();
@@ -714,27 +923,120 @@ var listCommand = new Command3().name("list").description("List available compon
714
923
  }
715
924
  console.log();
716
925
  }
717
- console.log(chalk4.dim("Add a component: npx nativeui add <component>"));
926
+ console.log(chalk4.dim("Add a component: npx mcellui add <component>"));
718
927
  console.log();
719
928
  } catch (error) {
720
929
  spinner.fail("Failed to fetch components");
721
930
  console.error(error);
722
931
  process.exit(1);
723
932
  }
724
- });
933
+ }
934
+ async function listInstalledComponents(options) {
935
+ const spinner = ora3();
936
+ try {
937
+ const cwd = path6.resolve(options.cwd || process.cwd());
938
+ const projectRoot = await getProjectRoot(cwd);
939
+ if (!projectRoot) {
940
+ console.log(chalk4.red("Could not find a valid project."));
941
+ console.log(chalk4.dim("Run `npx mcellui init` first."));
942
+ process.exit(1);
943
+ }
944
+ const config = await getConfig(projectRoot);
945
+ if (!config) {
946
+ console.log(chalk4.red("Project not initialized."));
947
+ console.log(chalk4.dim("Run `npx mcellui init` first."));
948
+ process.exit(1);
949
+ }
950
+ spinner.start("Scanning installed components...");
951
+ const componentsDir = path6.join(projectRoot, config.componentsPath);
952
+ const installedFiles = await getInstalledFiles(componentsDir);
953
+ if (installedFiles.length === 0) {
954
+ spinner.info("No components installed yet.");
955
+ console.log(chalk4.dim("\nAdd components with: npx mcellui add <component>"));
956
+ return;
957
+ }
958
+ spinner.text = "Fetching registry...";
959
+ const registry = await getRegistry();
960
+ spinner.text = "Comparing components...";
961
+ const installed = await getInstallStatus(installedFiles, registry, config);
962
+ spinner.stop();
963
+ const identical = installed.filter((c) => c.status === "identical");
964
+ const modified = installed.filter((c) => c.status === "modified");
965
+ const localOnly = installed.filter((c) => c.status === "local-only");
966
+ const installedNames = new Set(installed.map((c) => c.name));
967
+ const notInstalled = registry.filter((item) => !installedNames.has(item.name));
968
+ console.log();
969
+ console.log(chalk4.bold(`Installed Components (${installed.length})`));
970
+ console.log();
971
+ const categories = /* @__PURE__ */ new Map();
972
+ for (const comp of installed) {
973
+ const registryItem = registry.find((r) => r.name === comp.name);
974
+ const category = registryItem?.category || "Custom";
975
+ if (!categories.has(category)) {
976
+ categories.set(category, []);
977
+ }
978
+ categories.get(category).push(comp);
979
+ }
980
+ for (const [category, components] of categories) {
981
+ console.log(chalk4.cyan.bold(category));
982
+ for (const comp of components) {
983
+ let statusIcon;
984
+ let statusText;
985
+ switch (comp.status) {
986
+ case "identical":
987
+ statusIcon = chalk4.green("\u2713");
988
+ statusText = chalk4.dim("(up to date)");
989
+ break;
990
+ case "modified":
991
+ statusIcon = chalk4.yellow("\u26A0");
992
+ statusText = chalk4.yellow("(modified locally)");
993
+ break;
994
+ case "local-only":
995
+ statusIcon = chalk4.blue("?");
996
+ statusText = chalk4.dim("(custom component)");
997
+ break;
998
+ }
999
+ console.log(` ${statusIcon} ${chalk4.white(comp.name)} ${statusText}`);
1000
+ }
1001
+ console.log();
1002
+ }
1003
+ if (notInstalled.length > 0) {
1004
+ console.log(chalk4.dim("Not Installed"));
1005
+ const notInstalledNames = notInstalled.map((c) => c.name).slice(0, 10);
1006
+ const remaining = notInstalled.length - 10;
1007
+ console.log(chalk4.dim(` ${notInstalledNames.join(", ")}${remaining > 0 ? `, ... +${remaining} more` : ""}`));
1008
+ console.log();
1009
+ }
1010
+ console.log(chalk4.dim("\u2500".repeat(50)));
1011
+ const parts = [];
1012
+ if (identical.length > 0) parts.push(chalk4.green(`${identical.length} up to date`));
1013
+ if (modified.length > 0) parts.push(chalk4.yellow(`${modified.length} modified`));
1014
+ if (localOnly.length > 0) parts.push(chalk4.blue(`${localOnly.length} custom`));
1015
+ console.log(parts.join(" \u2022 "));
1016
+ if (modified.length > 0) {
1017
+ console.log();
1018
+ console.log(chalk4.dim("Sync modified components:"));
1019
+ console.log(chalk4.cyan(` npx mcellui diff`));
1020
+ }
1021
+ } catch (error) {
1022
+ spinner.fail("Failed to list installed components");
1023
+ console.error(error);
1024
+ process.exit(1);
1025
+ }
1026
+ }
725
1027
 
726
1028
  // src/commands/doctor.ts
727
1029
  import { Command as Command4 } from "commander";
728
1030
  import chalk5 from "chalk";
729
- import fs5 from "fs-extra";
730
- import path5 from "path";
1031
+ import fs6 from "fs-extra";
1032
+ import path7 from "path";
731
1033
  var REQUIRED_PEER_DEPS = [
732
1034
  { name: "react-native-reanimated", minVersion: "3.0.0" },
733
1035
  { name: "react-native-gesture-handler", minVersion: "2.0.0" },
734
1036
  { name: "react-native-safe-area-context", minVersion: "4.0.0" }
735
1037
  ];
736
1038
  var RECOMMENDED_DEPS = [
737
- { name: "@nativeui/core", reason: "Theme system and utilities" }
1039
+ { name: "@metacells/mcellui-core", reason: "Theme system and utilities" }
738
1040
  ];
739
1041
  function parseVersion(version) {
740
1042
  const cleaned = version.replace(/^[\^~>=<]+/, "").replace(/^workspace:\*?/, "");
@@ -779,7 +1081,7 @@ async function checkProjectType(projectRoot) {
779
1081
  name: "Project Type",
780
1082
  status: "fail",
781
1083
  message: "Not an Expo or React Native project",
782
- fix: "nativeui requires Expo or React Native"
1084
+ fix: "mcellui requires Expo or React Native"
783
1085
  };
784
1086
  }
785
1087
  return {
@@ -790,34 +1092,50 @@ async function checkProjectType(projectRoot) {
790
1092
  }
791
1093
  async function checkInitialized(projectRoot) {
792
1094
  const configFiles = [
1095
+ "mcellui.config.ts",
1096
+ "mcellui.config.js",
1097
+ "mcellui.config.json",
793
1098
  "nativeui.config.ts",
1099
+ // Legacy
794
1100
  "nativeui.config.js",
1101
+ // Legacy
795
1102
  "nativeui.config.json"
1103
+ // Legacy
796
1104
  ];
797
1105
  let foundConfig = null;
1106
+ let isLegacy = false;
798
1107
  for (const file of configFiles) {
799
- if (await fs5.pathExists(path5.join(projectRoot, file))) {
1108
+ if (await fs6.pathExists(path7.join(projectRoot, file))) {
800
1109
  foundConfig = file;
1110
+ isLegacy = file.startsWith("nativeui.");
801
1111
  break;
802
1112
  }
803
1113
  }
804
1114
  if (!foundConfig) {
805
1115
  return {
806
- name: "NativeUI Config",
1116
+ name: "mcellui Config",
807
1117
  status: "fail",
808
- message: "nativeui.config.ts not found",
809
- fix: "Run: npx nativeui init"
1118
+ message: "mcellui.config.ts not found",
1119
+ fix: "Run: npx mcellui init"
1120
+ };
1121
+ }
1122
+ if (isLegacy) {
1123
+ return {
1124
+ name: "mcellui Config",
1125
+ status: "warn",
1126
+ message: `Found legacy config: ${foundConfig}`,
1127
+ fix: "Consider renaming to mcellui.config.ts"
810
1128
  };
811
1129
  }
812
1130
  try {
813
- const configPath = path5.join(projectRoot, foundConfig);
814
- const content = await fs5.readFile(configPath, "utf-8");
1131
+ const configPath = path7.join(projectRoot, foundConfig);
1132
+ const content = await fs6.readFile(configPath, "utf-8");
815
1133
  if (foundConfig.endsWith(".ts") || foundConfig.endsWith(".js")) {
816
1134
  const hasDefineConfig = content.includes("defineConfig");
817
1135
  const hasExport = content.includes("export default");
818
1136
  if (!hasExport) {
819
1137
  return {
820
- name: "NativeUI Config",
1138
+ name: "mcellui Config",
821
1139
  status: "warn",
822
1140
  message: "Config file missing default export",
823
1141
  fix: "Add: export default defineConfig({ ... })"
@@ -825,7 +1143,7 @@ async function checkInitialized(projectRoot) {
825
1143
  }
826
1144
  if (!hasDefineConfig) {
827
1145
  return {
828
- name: "NativeUI Config",
1146
+ name: "mcellui Config",
829
1147
  status: "warn",
830
1148
  message: "Config not using defineConfig helper",
831
1149
  fix: "Wrap config with: defineConfig({ ... })"
@@ -837,21 +1155,21 @@ async function checkInitialized(projectRoot) {
837
1155
  JSON.parse(content);
838
1156
  } catch {
839
1157
  return {
840
- name: "NativeUI Config",
1158
+ name: "mcellui Config",
841
1159
  status: "fail",
842
1160
  message: "Invalid JSON in config file",
843
- fix: "Check JSON syntax in nativeui.config.json"
1161
+ fix: "Check JSON syntax in mcellui.config.json"
844
1162
  };
845
1163
  }
846
1164
  }
847
1165
  return {
848
- name: "NativeUI Config",
1166
+ name: "mcellui Config",
849
1167
  status: "pass",
850
1168
  message: `Found ${foundConfig}`
851
1169
  };
852
1170
  } catch (error) {
853
1171
  return {
854
- name: "NativeUI Config",
1172
+ name: "mcellui Config",
855
1173
  status: "fail",
856
1174
  message: "Could not read config file",
857
1175
  fix: error instanceof Error ? error.message : "Check file permissions"
@@ -863,12 +1181,19 @@ async function checkPaths(projectRoot) {
863
1181
  const defaultUtilsPath = "./lib/utils";
864
1182
  let componentsPathValue = defaultComponentsPath;
865
1183
  let utilsPathValue = defaultUtilsPath;
866
- const configFiles = ["nativeui.config.ts", "nativeui.config.js", "nativeui.config.json"];
1184
+ const configFiles = [
1185
+ "mcellui.config.ts",
1186
+ "mcellui.config.js",
1187
+ "mcellui.config.json",
1188
+ "nativeui.config.ts",
1189
+ "nativeui.config.js",
1190
+ "nativeui.config.json"
1191
+ ];
867
1192
  for (const file of configFiles) {
868
- const configPath = path5.join(projectRoot, file);
869
- if (await fs5.pathExists(configPath)) {
1193
+ const configPath = path7.join(projectRoot, file);
1194
+ if (await fs6.pathExists(configPath)) {
870
1195
  try {
871
- const content = await fs5.readFile(configPath, "utf-8");
1196
+ const content = await fs6.readFile(configPath, "utf-8");
872
1197
  const componentsMatch = content.match(/componentsPath:\s*['"]([^'"]+)['"]/);
873
1198
  const utilsMatch = content.match(/utilsPath:\s*['"]([^'"]+)['"]/);
874
1199
  if (componentsMatch) componentsPathValue = componentsMatch[1];
@@ -878,16 +1203,16 @@ async function checkPaths(projectRoot) {
878
1203
  break;
879
1204
  }
880
1205
  }
881
- const componentsPath = path5.join(projectRoot, componentsPathValue);
882
- const utilsPath = path5.join(projectRoot, utilsPathValue);
883
- const componentsExist = await fs5.pathExists(componentsPath);
884
- const utilsExist = await fs5.pathExists(utilsPath);
1206
+ const componentsPath = path7.join(projectRoot, componentsPathValue);
1207
+ const utilsPath = path7.join(projectRoot, utilsPathValue);
1208
+ const componentsExist = await fs6.pathExists(componentsPath);
1209
+ const utilsExist = await fs6.pathExists(utilsPath);
885
1210
  if (!componentsExist && !utilsExist) {
886
1211
  return {
887
1212
  name: "Component Paths",
888
1213
  status: "warn",
889
1214
  message: "Component and utils directories not created yet",
890
- fix: `Add a component: npx nativeui add button`
1215
+ fix: `Add a component: npx mcellui add button`
891
1216
  };
892
1217
  }
893
1218
  if (!componentsExist) {
@@ -895,7 +1220,7 @@ async function checkPaths(projectRoot) {
895
1220
  name: "Component Paths",
896
1221
  status: "warn",
897
1222
  message: `Components directory not found: ${componentsPathValue}`,
898
- fix: "Add a component to create it: npx nativeui add button"
1223
+ fix: "Add a component to create it: npx mcellui add button"
899
1224
  };
900
1225
  }
901
1226
  return {
@@ -978,10 +1303,10 @@ async function checkBabelConfig(projectRoot) {
978
1303
  let babelConfigPath = null;
979
1304
  let babelContent = null;
980
1305
  for (const configFile of babelConfigPaths) {
981
- const fullPath = path5.join(projectRoot, configFile);
982
- if (await fs5.pathExists(fullPath)) {
1306
+ const fullPath = path7.join(projectRoot, configFile);
1307
+ if (await fs6.pathExists(fullPath)) {
983
1308
  babelConfigPath = configFile;
984
- babelContent = await fs5.readFile(fullPath, "utf-8");
1309
+ babelContent = await fs6.readFile(fullPath, "utf-8");
985
1310
  break;
986
1311
  }
987
1312
  }
@@ -1009,8 +1334,8 @@ async function checkBabelConfig(projectRoot) {
1009
1334
  };
1010
1335
  }
1011
1336
  async function checkTypeScript(projectRoot) {
1012
- const tsconfigPath = path5.join(projectRoot, "tsconfig.json");
1013
- if (!await fs5.pathExists(tsconfigPath)) {
1337
+ const tsconfigPath = path7.join(projectRoot, "tsconfig.json");
1338
+ if (!await fs6.pathExists(tsconfigPath)) {
1014
1339
  return {
1015
1340
  name: "TypeScript",
1016
1341
  status: "warn",
@@ -1019,7 +1344,7 @@ async function checkTypeScript(projectRoot) {
1019
1344
  };
1020
1345
  }
1021
1346
  try {
1022
- const tsconfig = await fs5.readJson(tsconfigPath);
1347
+ const tsconfig = await fs6.readJson(tsconfigPath);
1023
1348
  const hasPathAliases = tsconfig.compilerOptions?.paths;
1024
1349
  if (!hasPathAliases) {
1025
1350
  return {
@@ -1083,7 +1408,7 @@ async function checkExpoGo(projectRoot) {
1083
1408
  }
1084
1409
  function printReport(report) {
1085
1410
  console.log();
1086
- console.log(chalk5.bold("NativeUI Doctor"));
1411
+ console.log(chalk5.bold("mcellui Doctor"));
1087
1412
  console.log(chalk5.dim("Checking your project setup..."));
1088
1413
  console.log();
1089
1414
  console.log(chalk5.dim("Project:"), report.projectRoot);
@@ -1125,7 +1450,7 @@ function printReport(report) {
1125
1450
  }
1126
1451
  var doctorCommand = new Command4().name("doctor").description("Check project setup and diagnose common issues").option("--cwd <path>", "Working directory", process.cwd()).option("--json", "Output results as JSON").action(async (options) => {
1127
1452
  try {
1128
- const cwd = path5.resolve(options.cwd);
1453
+ const cwd = path7.resolve(options.cwd);
1129
1454
  const projectRoot = await getProjectRoot(cwd);
1130
1455
  if (!projectRoot) {
1131
1456
  console.log(chalk5.red("Could not find a valid project."));
@@ -1177,31 +1502,31 @@ var doctorCommand = new Command4().name("doctor").description("Check project set
1177
1502
  import { Command as Command5 } from "commander";
1178
1503
  import chalk6 from "chalk";
1179
1504
  import ora4 from "ora";
1180
- import fs6 from "fs-extra";
1181
- import path6 from "path";
1182
- import crypto from "crypto";
1183
- var diffCommand = new Command5().name("diff").description("Check for component updates against the registry").option("--cwd <path>", "Working directory", process.cwd()).option("--json", "Output as JSON").option("-v, --verbose", "Show detailed diff information").action(async (options) => {
1505
+ import fs7 from "fs-extra";
1506
+ import path8 from "path";
1507
+ import * as Diff from "diff";
1508
+ var diffCommand = new Command5().name("diff").description("Compare locally installed components against the registry source").argument("[components...]", "Component names to diff (optional, diffs all if omitted)").option("--cwd <path>", "Working directory", process.cwd()).option("--list", "Only list components with differences").option("--json", "Output as JSON").action(async (components, options) => {
1184
1509
  const spinner = ora4();
1185
1510
  try {
1186
- const cwd = path6.resolve(options.cwd);
1511
+ const cwd = path8.resolve(options.cwd);
1187
1512
  const projectRoot = await getProjectRoot(cwd);
1188
1513
  if (!projectRoot) {
1189
1514
  console.log(chalk6.red("Could not find a valid project."));
1190
- console.log(chalk6.dim("Run `npx nativeui init` first."));
1515
+ console.log(chalk6.dim("Run `npx mcellui init` first."));
1191
1516
  process.exit(1);
1192
1517
  }
1193
1518
  const config = await getConfig(projectRoot);
1194
1519
  if (!config) {
1195
1520
  console.log(chalk6.red("Project not initialized."));
1196
- console.log(chalk6.dim("Run `npx nativeui init` first."));
1521
+ console.log(chalk6.dim("Run `npx mcellui init` first."));
1197
1522
  process.exit(1);
1198
1523
  }
1199
1524
  spinner.start("Scanning installed components...");
1200
- const componentsDir = path6.join(projectRoot, config.componentsPath);
1201
- const installedFiles = await getInstalledComponents(componentsDir);
1525
+ const componentsDir = path8.join(projectRoot, config.componentsPath);
1526
+ const installedFiles = await getInstalledFiles(componentsDir);
1202
1527
  if (installedFiles.length === 0) {
1203
1528
  spinner.info("No components installed yet.");
1204
- console.log(chalk6.dim("\nAdd components with: npx nativeui add <component>"));
1529
+ console.log(chalk6.dim("\nAdd components with: npx mcellui add <component>"));
1205
1530
  return;
1206
1531
  }
1207
1532
  spinner.text = "Fetching registry...";
@@ -1209,18 +1534,34 @@ var diffCommand = new Command5().name("diff").description("Check for component u
1209
1534
  const registryMap = /* @__PURE__ */ new Map();
1210
1535
  for (const item of registry) {
1211
1536
  for (const file of item.files) {
1212
- const fileName = path6.basename(file);
1537
+ const fileName = path8.basename(file);
1213
1538
  registryMap.set(fileName, item);
1214
1539
  }
1215
1540
  }
1541
+ let filesToCompare = installedFiles;
1542
+ if (components.length > 0) {
1543
+ const componentFileNames = components.map((c) => {
1544
+ return c.endsWith(".tsx") ? c : `${c}.tsx`;
1545
+ });
1546
+ filesToCompare = installedFiles.filter((file) => {
1547
+ const fileName = path8.basename(file);
1548
+ return componentFileNames.includes(fileName);
1549
+ });
1550
+ if (filesToCompare.length === 0) {
1551
+ spinner.fail(`None of the specified components found: ${components.join(", ")}`);
1552
+ process.exit(1);
1553
+ }
1554
+ }
1216
1555
  spinner.text = "Comparing components...";
1217
1556
  const results = [];
1218
- for (const localFile of installedFiles) {
1219
- const fileName = path6.basename(localFile);
1557
+ for (const localFile of filesToCompare) {
1558
+ const fileName = path8.basename(localFile);
1559
+ const componentName = fileName.replace(/\.tsx?$/, "");
1220
1560
  const registryItem = registryMap.get(fileName);
1221
1561
  if (!registryItem) {
1222
1562
  results.push({
1223
- name: fileName.replace(/\.tsx?$/, ""),
1563
+ name: componentName,
1564
+ fileName,
1224
1565
  status: "local-only",
1225
1566
  localFile
1226
1567
  });
@@ -1231,6 +1572,7 @@ var diffCommand = new Command5().name("diff").description("Check for component u
1231
1572
  if (!component) {
1232
1573
  results.push({
1233
1574
  name: registryItem.name,
1575
+ fileName,
1234
1576
  status: "error",
1235
1577
  error: "Failed to fetch from registry"
1236
1578
  });
@@ -1240,31 +1582,42 @@ var diffCommand = new Command5().name("diff").description("Check for component u
1240
1582
  if (!registryFile) {
1241
1583
  results.push({
1242
1584
  name: registryItem.name,
1585
+ fileName,
1243
1586
  status: "error",
1244
1587
  error: "File not found in registry"
1245
1588
  });
1246
1589
  continue;
1247
1590
  }
1248
- const localContent = await fs6.readFile(localFile, "utf-8");
1249
- const transformedRegistryContent = transformImports2(registryFile.content, config);
1250
- const localHash = hashContent(localContent);
1251
- const registryHash = hashContent(transformedRegistryContent);
1252
- if (localHash === registryHash) {
1591
+ const localContent = await fs7.readFile(localFile, "utf-8");
1592
+ const normalizedLocal = normalizeForComparison(localContent);
1593
+ const normalizedRegistry = normalizeForComparison(registryFile.content);
1594
+ if (normalizedLocal === normalizedRegistry) {
1253
1595
  results.push({
1254
1596
  name: registryItem.name,
1255
- status: "up-to-date",
1597
+ fileName,
1598
+ status: "identical",
1256
1599
  localFile
1257
1600
  });
1258
1601
  } else {
1602
+ const diffOutput = Diff.createPatch(
1603
+ fileName,
1604
+ normalizedRegistry,
1605
+ normalizedLocal,
1606
+ "registry",
1607
+ "local"
1608
+ );
1259
1609
  results.push({
1260
1610
  name: registryItem.name,
1261
- status: "changed",
1262
- localFile
1611
+ fileName,
1612
+ status: "modified",
1613
+ localFile,
1614
+ diff: diffOutput
1263
1615
  });
1264
1616
  }
1265
1617
  } catch (error) {
1266
1618
  results.push({
1267
1619
  name: registryItem.name,
1620
+ fileName,
1268
1621
  status: "error",
1269
1622
  error: String(error)
1270
1623
  });
@@ -1273,100 +1626,71 @@ var diffCommand = new Command5().name("diff").description("Check for component u
1273
1626
  spinner.stop();
1274
1627
  if (options.json) {
1275
1628
  console.log(JSON.stringify(results, null, 2));
1276
- return;
1629
+ const hasChanges2 = results.some((r) => r.status === "modified");
1630
+ process.exit(hasChanges2 ? 1 : 0);
1277
1631
  }
1278
- printResults(results, options.verbose);
1632
+ printResults(results, options.list);
1633
+ const hasChanges = results.some((r) => r.status === "modified");
1634
+ process.exit(hasChanges ? 1 : 0);
1279
1635
  } catch (error) {
1280
- spinner.fail("Failed to check for updates");
1636
+ spinner.fail("Failed to diff components");
1281
1637
  console.error(error);
1282
1638
  process.exit(1);
1283
1639
  }
1284
1640
  });
1285
- async function getInstalledComponents(componentsDir) {
1286
- if (!await fs6.pathExists(componentsDir)) {
1287
- return [];
1288
- }
1289
- const files = await fs6.readdir(componentsDir);
1290
- const componentFiles = [];
1291
- for (const file of files) {
1292
- if (file.endsWith(".tsx") || file.endsWith(".ts")) {
1293
- if (file === "index.ts" || file === "index.tsx") {
1294
- continue;
1295
- }
1296
- componentFiles.push(path6.join(componentsDir, file));
1297
- }
1298
- }
1299
- return componentFiles;
1300
- }
1301
- function hashContent(content) {
1302
- const normalized = content.replace(/\r\n/g, "\n").replace(/\s+$/gm, "").trim();
1303
- return crypto.createHash("md5").update(normalized).digest("hex");
1304
- }
1305
- function transformImports2(code, config) {
1306
- let transformed = code;
1307
- const utilsAlias = config.aliases?.utils || "@/lib/utils";
1308
- if (utilsAlias === "@/lib/utils") {
1309
- return transformed;
1310
- }
1311
- transformed = transformed.replace(
1312
- /from ['"]@\/lib\/utils['"]/g,
1313
- `from '${utilsAlias}'`
1314
- );
1315
- return transformed;
1316
- }
1317
- function printResults(results, verbose) {
1318
- const upToDate = results.filter((r) => r.status === "up-to-date");
1319
- const changed = results.filter((r) => r.status === "changed");
1641
+ function printResults(results, listOnly) {
1642
+ const identical = results.filter((r) => r.status === "identical");
1643
+ const modified = results.filter((r) => r.status === "modified");
1320
1644
  const localOnly = results.filter((r) => r.status === "local-only");
1321
1645
  const errors = results.filter((r) => r.status === "error");
1322
1646
  console.log();
1323
- console.log(chalk6.bold("Component Diff Report"));
1324
- console.log(chalk6.dim("\u2500".repeat(40)));
1325
- console.log();
1326
- const total = results.length;
1327
- console.log(`Found ${chalk6.bold(total)} installed component${total !== 1 ? "s" : ""}`);
1647
+ console.log(chalk6.bold("Comparing components..."));
1328
1648
  console.log();
1329
- if (changed.length > 0) {
1330
- console.log(chalk6.yellow(`\u26A1 ${changed.length} update${changed.length !== 1 ? "s" : ""} available:`));
1331
- for (const item of changed) {
1332
- console.log(` ${chalk6.yellow("\u25CF")} ${item.name}`);
1333
- }
1334
- console.log();
1335
- console.log(chalk6.dim(" Update with: npx nativeui add <component> --overwrite"));
1336
- console.log();
1337
- }
1338
- if (upToDate.length > 0) {
1339
- console.log(chalk6.green(`\u2713 ${upToDate.length} up to date:`));
1340
- if (verbose) {
1341
- for (const item of upToDate) {
1342
- console.log(` ${chalk6.green("\u25CF")} ${item.name}`);
1343
- }
1344
- } else {
1345
- const names = upToDate.map((r) => r.name).join(", ");
1346
- console.log(chalk6.dim(` ${names}`));
1649
+ for (const result of results) {
1650
+ switch (result.status) {
1651
+ case "identical":
1652
+ console.log(`${chalk6.green("\u2713")} ${result.fileName} ${chalk6.dim("(identical)")}`);
1653
+ break;
1654
+ case "modified":
1655
+ console.log(`${chalk6.red("\u2717")} ${result.fileName} ${chalk6.yellow("(modified)")}`);
1656
+ if (!listOnly && result.diff) {
1657
+ printColoredDiff(result.diff);
1658
+ }
1659
+ break;
1660
+ case "local-only":
1661
+ console.log(`${chalk6.yellow("\u26A0")} ${result.fileName} ${chalk6.dim("(not in registry)")}`);
1662
+ break;
1663
+ case "error":
1664
+ console.log(`${chalk6.red("!")} ${result.fileName} ${chalk6.red(`(error: ${result.error})`)}`);
1665
+ break;
1347
1666
  }
1348
- console.log();
1349
1667
  }
1350
- if (localOnly.length > 0) {
1351
- console.log(chalk6.blue(`\u25D0 ${localOnly.length} local-only (not in registry):`));
1352
- for (const item of localOnly) {
1353
- console.log(` ${chalk6.blue("\u25CF")} ${item.name}`);
1354
- }
1668
+ console.log();
1669
+ const parts = [];
1670
+ if (identical.length > 0) parts.push(`${identical.length} identical`);
1671
+ if (modified.length > 0) parts.push(`${modified.length} modified`);
1672
+ if (localOnly.length > 0) parts.push(`${localOnly.length} custom`);
1673
+ if (errors.length > 0) parts.push(`${errors.length} errors`);
1674
+ console.log(chalk6.dim(`Summary: ${parts.join(", ")}`));
1675
+ if (modified.length > 0) {
1355
1676
  console.log();
1677
+ console.log(chalk6.dim("Update modified components with:"));
1678
+ console.log(chalk6.cyan(` npx mcellui add ${modified.map((m) => m.name).join(" ")} --overwrite`));
1356
1679
  }
1357
- if (errors.length > 0) {
1358
- console.log(chalk6.red(`\u2717 ${errors.length} error${errors.length !== 1 ? "s" : ""}:`));
1359
- for (const item of errors) {
1360
- console.log(` ${chalk6.red("\u25CF")} ${item.name}: ${chalk6.dim(item.error)}`);
1680
+ }
1681
+ function printColoredDiff(diffOutput) {
1682
+ const lines = diffOutput.split("\n");
1683
+ const contentLines = lines.slice(4);
1684
+ for (const line of contentLines) {
1685
+ if (line.startsWith("+") && !line.startsWith("+++")) {
1686
+ console.log(chalk6.green(` ${line}`));
1687
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
1688
+ console.log(chalk6.red(` ${line}`));
1689
+ } else if (line.startsWith("@@")) {
1690
+ console.log(chalk6.cyan(` ${line}`));
1691
+ } else if (line.trim()) {
1692
+ console.log(chalk6.dim(` ${line}`));
1361
1693
  }
1362
- console.log();
1363
- }
1364
- if (changed.length === 0 && errors.length === 0) {
1365
- console.log(chalk6.green("All registry components are up to date!"));
1366
- } else if (changed.length > 0) {
1367
- console.log(
1368
- chalk6.yellow(`Run ${chalk6.cyan("npx nativeui add " + changed.map((c) => c.name).join(" ") + " --overwrite")} to update`)
1369
- );
1370
1694
  }
1371
1695
  }
1372
1696
 
@@ -1375,13 +1699,12 @@ import { Command as Command6 } from "commander";
1375
1699
  import chalk7 from "chalk";
1376
1700
  import ora5 from "ora";
1377
1701
  import prompts3 from "prompts";
1378
- import fs7 from "fs-extra";
1379
- import path7 from "path";
1380
- import crypto2 from "crypto";
1702
+ import fs8 from "fs-extra";
1703
+ import path9 from "path";
1381
1704
  var updateCommand = new Command6().name("update").description("Update installed components to latest registry versions").argument("[components...]", "Specific components to update (default: all outdated)").option("-y, --yes", "Skip confirmation prompt").option("--all", "Update all components (including up-to-date)").option("--dry-run", "Show what would be updated without making changes").option("--cwd <path>", "Working directory", process.cwd()).action(async (components, options) => {
1382
1705
  const spinner = ora5();
1383
1706
  try {
1384
- const cwd = path7.resolve(options.cwd);
1707
+ const cwd = path9.resolve(options.cwd);
1385
1708
  const projectRoot = await getProjectRoot(cwd);
1386
1709
  if (!projectRoot) {
1387
1710
  console.log(chalk7.red("Could not find a valid project."));
@@ -1395,7 +1718,7 @@ var updateCommand = new Command6().name("update").description("Update installed
1395
1718
  process.exit(1);
1396
1719
  }
1397
1720
  spinner.start("Checking for updates...");
1398
- const componentsDir = path7.join(projectRoot, config.componentsPath);
1721
+ const componentsDir = path9.join(projectRoot, config.componentsPath);
1399
1722
  const diffs = await getComponentDiffs(componentsDir, config);
1400
1723
  if (diffs.length === 0) {
1401
1724
  spinner.info("No components installed yet.");
@@ -1457,12 +1780,12 @@ var updateCommand = new Command6().name("update").description("Update installed
1457
1780
  failCount++;
1458
1781
  continue;
1459
1782
  }
1460
- const targetDir = path7.join(projectRoot, config.componentsPath);
1783
+ const targetDir = path9.join(projectRoot, config.componentsPath);
1461
1784
  for (const file of component.files) {
1462
- const targetPath = path7.join(targetDir, file.name);
1463
- await fs7.ensureDir(targetDir);
1464
- const transformedContent = transformImports3(file.content, config);
1465
- await fs7.writeFile(targetPath, transformedContent);
1785
+ const targetPath = path9.join(targetDir, file.name);
1786
+ await fs8.ensureDir(targetDir);
1787
+ const transformedContent = transformToInstalled(file.content, config);
1788
+ await fs8.writeFile(targetPath, transformedContent);
1466
1789
  }
1467
1790
  spinner.succeed(`Updated ${comp.name}`);
1468
1791
  successCount++;
@@ -1503,18 +1826,18 @@ var updateCommand = new Command6().name("update").description("Update installed
1503
1826
  }
1504
1827
  });
1505
1828
  async function getComponentDiffs(componentsDir, config) {
1506
- if (!await fs7.pathExists(componentsDir)) {
1829
+ if (!await fs8.pathExists(componentsDir)) {
1507
1830
  return [];
1508
1831
  }
1509
1832
  const registry = await getRegistry();
1510
1833
  const registryMap = /* @__PURE__ */ new Map();
1511
1834
  for (const item of registry) {
1512
1835
  for (const file of item.files) {
1513
- const fileName = path7.basename(file);
1836
+ const fileName = path9.basename(file);
1514
1837
  registryMap.set(fileName, item);
1515
1838
  }
1516
1839
  }
1517
- const files = await fs7.readdir(componentsDir);
1840
+ const files = await fs8.readdir(componentsDir);
1518
1841
  const results = [];
1519
1842
  for (const file of files) {
1520
1843
  if (!file.endsWith(".tsx") && !file.endsWith(".ts")) {
@@ -1527,7 +1850,7 @@ async function getComponentDiffs(componentsDir, config) {
1527
1850
  if (!registryItem) {
1528
1851
  continue;
1529
1852
  }
1530
- const localFile = path7.join(componentsDir, file);
1853
+ const localFile = path9.join(componentsDir, file);
1531
1854
  const hasUpdate = await checkForUpdate(localFile, registryItem, config);
1532
1855
  results.push({
1533
1856
  name: registryItem.name,
@@ -1541,46 +1864,29 @@ async function checkForUpdate(localFile, registryItem, config) {
1541
1864
  try {
1542
1865
  const component = await fetchComponent(registryItem.name);
1543
1866
  if (!component) return false;
1544
- const fileName = path7.basename(localFile);
1867
+ const fileName = path9.basename(localFile);
1545
1868
  const registryFile = component.files.find((f) => f.name === fileName);
1546
1869
  if (!registryFile) return false;
1547
- const localContent = await fs7.readFile(localFile, "utf-8");
1548
- const transformedRegistryContent = transformImports3(registryFile.content, config);
1549
- const localHash = hashContent2(localContent);
1550
- const registryHash = hashContent2(transformedRegistryContent);
1551
- return localHash !== registryHash;
1870
+ const localContent = await fs8.readFile(localFile, "utf-8");
1871
+ const normalizedLocal = normalizeForComparison(localContent);
1872
+ const normalizedRegistry = normalizeForComparison(registryFile.content);
1873
+ return normalizedLocal !== normalizedRegistry;
1552
1874
  } catch {
1553
1875
  return false;
1554
1876
  }
1555
1877
  }
1556
- function hashContent2(content) {
1557
- const normalized = content.replace(/\r\n/g, "\n").replace(/\s+$/gm, "").trim();
1558
- return crypto2.createHash("md5").update(normalized).digest("hex");
1559
- }
1560
- function transformImports3(code, config) {
1561
- let transformed = code;
1562
- const utilsAlias = config.aliases?.utils || "@/lib/utils";
1563
- if (utilsAlias === "@/lib/utils") {
1564
- return transformed;
1565
- }
1566
- transformed = transformed.replace(
1567
- /from ['"]@\/lib\/utils['"]/g,
1568
- `from '${utilsAlias}'`
1569
- );
1570
- return transformed;
1571
- }
1572
1878
 
1573
1879
  // src/commands/create.ts
1574
1880
  import { Command as Command7 } from "commander";
1575
1881
  import chalk8 from "chalk";
1576
1882
  import ora6 from "ora";
1577
1883
  import prompts4 from "prompts";
1578
- import fs8 from "fs-extra";
1579
- import path8 from "path";
1884
+ import fs9 from "fs-extra";
1885
+ import path10 from "path";
1580
1886
  var createCommand = new Command7().name("create").description("Scaffold a new custom component").argument("<name>", "Component name (e.g., my-button, ProfileCard)").option("-t, --template <type>", "Component template: basic, animated, pressable, input", "basic").option("--forward-ref", "Include forwardRef pattern").option("-y, --yes", "Skip confirmation").option("--cwd <path>", "Working directory", process.cwd()).action(async (name, options) => {
1581
1887
  const spinner = ora6();
1582
1888
  try {
1583
- const cwd = path8.resolve(options.cwd);
1889
+ const cwd = path10.resolve(options.cwd);
1584
1890
  const projectRoot = await getProjectRoot(cwd);
1585
1891
  if (!projectRoot) {
1586
1892
  console.log(chalk8.red("Could not find a valid project."));
@@ -1595,9 +1901,9 @@ var createCommand = new Command7().name("create").description("Scaffold a new cu
1595
1901
  }
1596
1902
  const componentName = toPascalCase(name);
1597
1903
  const fileName = toKebabCase(name) + ".tsx";
1598
- const targetDir = path8.join(projectRoot, config.componentsPath);
1599
- const targetPath = path8.join(targetDir, fileName);
1600
- if (await fs8.pathExists(targetPath)) {
1904
+ const targetDir = path10.join(projectRoot, config.componentsPath);
1905
+ const targetPath = path10.join(targetDir, fileName);
1906
+ if (await fs9.pathExists(targetPath)) {
1601
1907
  console.log(chalk8.red(`Component already exists: ${fileName}`));
1602
1908
  console.log(chalk8.dim(`Path: ${targetPath}`));
1603
1909
  process.exit(1);
@@ -1624,8 +1930,8 @@ var createCommand = new Command7().name("create").description("Scaffold a new cu
1624
1930
  }
1625
1931
  spinner.start("Creating component...");
1626
1932
  const code = generateComponent(componentName, options.template, options.forwardRef);
1627
- await fs8.ensureDir(targetDir);
1628
- await fs8.writeFile(targetPath, code);
1933
+ await fs9.ensureDir(targetDir);
1934
+ await fs9.writeFile(targetPath, code);
1629
1935
  spinner.succeed(`Created ${fileName}`);
1630
1936
  console.log();
1631
1937
  console.log(chalk8.bold("Next steps:"));
@@ -1658,7 +1964,7 @@ function generateBasicComponent(name, forwardRef) {
1658
1964
  if (forwardRef) {
1659
1965
  return `import React, { forwardRef } from 'react';
1660
1966
  import { View, Text, StyleSheet, ViewProps } from 'react-native';
1661
- import { useTheme } from '@nativeui/core';
1967
+ import { useTheme } from '@metacells/mcellui-core';
1662
1968
 
1663
1969
  export interface ${name}Props extends ViewProps {
1664
1970
  /**
@@ -1718,7 +2024,7 @@ const styles = StyleSheet.create({
1718
2024
  }
1719
2025
  return `import React from 'react';
1720
2026
  import { View, Text, StyleSheet, ViewProps } from 'react-native';
1721
- import { useTheme } from '@nativeui/core';
2027
+ import { useTheme } from '@metacells/mcellui-core';
1722
2028
 
1723
2029
  export interface ${name}Props extends ViewProps {
1724
2030
  /**
@@ -1785,7 +2091,7 @@ import Animated, {
1785
2091
  useSharedValue,
1786
2092
  withSpring,
1787
2093
  } from 'react-native-reanimated';
1788
- import { useTheme } from '@nativeui/core';
2094
+ import { useTheme } from '@metacells/mcellui-core';
1789
2095
 
1790
2096
  export interface ${name}Props extends ViewProps {
1791
2097
  /**
@@ -1854,7 +2160,7 @@ import Animated, {
1854
2160
  useSharedValue,
1855
2161
  withTiming,
1856
2162
  } from 'react-native-reanimated';
1857
- import { useTheme } from '@nativeui/core';
2163
+ import { useTheme } from '@metacells/mcellui-core';
1858
2164
 
1859
2165
  const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
1860
2166
 
@@ -1947,7 +2253,7 @@ import Animated, {
1947
2253
  useSharedValue,
1948
2254
  withTiming,
1949
2255
  } from 'react-native-reanimated';
1950
- import { useTheme } from '@nativeui/core';
2256
+ import { useTheme } from '@metacells/mcellui-core';
1951
2257
 
1952
2258
  const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
1953
2259
 
@@ -2037,7 +2343,7 @@ import Animated, {
2037
2343
  useSharedValue,
2038
2344
  withTiming,
2039
2345
  } from 'react-native-reanimated';
2040
- import { useTheme } from '@nativeui/core';
2346
+ import { useTheme } from '@metacells/mcellui-core';
2041
2347
 
2042
2348
  export interface ${name}Props extends TextInputProps {
2043
2349
  /**
@@ -2162,7 +2468,7 @@ import Animated, {
2162
2468
  useSharedValue,
2163
2469
  withTiming,
2164
2470
  } from 'react-native-reanimated';
2165
- import { useTheme } from '@nativeui/core';
2471
+ import { useTheme } from '@metacells/mcellui-core';
2166
2472
 
2167
2473
  export interface ${name}Props extends TextInputProps {
2168
2474
  /**
@@ -2281,8 +2587,8 @@ import { Command as Command8 } from "commander";
2281
2587
  import chalk9 from "chalk";
2282
2588
  import prompts5 from "prompts";
2283
2589
  import ora7 from "ora";
2284
- import fs9 from "fs-extra";
2285
- import path9 from "path";
2590
+ import fs10 from "fs-extra";
2591
+ import path11 from "path";
2286
2592
  function groupByCategory(items) {
2287
2593
  const groups = {};
2288
2594
  for (const item of items) {
@@ -2339,21 +2645,9 @@ function formatComponentChoice(item, installed) {
2339
2645
  disabled: isInstalled
2340
2646
  };
2341
2647
  }
2342
- function transformImports4(code, config) {
2343
- let transformed = code;
2344
- const utilsAlias = config.aliases?.utils || "@/lib/utils";
2345
- if (utilsAlias === "@/lib/utils") {
2346
- return transformed;
2347
- }
2348
- transformed = transformed.replace(
2349
- /from ['"]@\/lib\/utils['"]/g,
2350
- `from '${utilsAlias}'`
2351
- );
2352
- return transformed;
2353
- }
2354
2648
  var pickCommand = new Command8().name("pick").description("Interactively browse and select components to add").option("--all", "Show all components without category selection").option("-o, --overwrite", "Overwrite existing files").option("--cwd <path>", "Working directory", process.cwd()).action(async (options) => {
2355
2649
  console.log(chalk9.bold("\n\u{1F3A8} nativeui Component Picker\n"));
2356
- const cwd = path9.resolve(options.cwd);
2650
+ const cwd = path11.resolve(options.cwd);
2357
2651
  const projectRoot = await getProjectRoot(cwd);
2358
2652
  if (!projectRoot) {
2359
2653
  console.log(chalk9.red("Could not find a valid project."));
@@ -2372,10 +2666,10 @@ var pickCommand = new Command8().name("pick").description("Interactively browse
2372
2666
  return;
2373
2667
  }
2374
2668
  spinner.succeed(`Loaded ${registry.length} components`);
2375
- const componentsDir = path9.join(projectRoot, config.componentsPath);
2669
+ const componentsDir = path11.join(projectRoot, config.componentsPath);
2376
2670
  const installed = /* @__PURE__ */ new Set();
2377
- if (fs9.existsSync(componentsDir)) {
2378
- const files = fs9.readdirSync(componentsDir);
2671
+ if (fs10.existsSync(componentsDir)) {
2672
+ const files = fs10.readdirSync(componentsDir);
2379
2673
  for (const file of files) {
2380
2674
  if (file.endsWith(".tsx")) {
2381
2675
  installed.add(file.replace(".tsx", ""));
@@ -2458,16 +2752,16 @@ ${installed.size} already installed, ${availableCount} available
2458
2752
  installSpinner.fail(`Component "${name}" not found`);
2459
2753
  continue;
2460
2754
  }
2461
- const targetDir = path9.join(projectRoot, config.componentsPath);
2755
+ const targetDir = path11.join(projectRoot, config.componentsPath);
2462
2756
  for (const file of component.files) {
2463
- const targetPath = path9.join(targetDir, file.name);
2464
- if (await fs9.pathExists(targetPath) && !options.overwrite) {
2757
+ const targetPath = path11.join(targetDir, file.name);
2758
+ if (await fs10.pathExists(targetPath) && !options.overwrite) {
2465
2759
  installSpinner.warn(`${name}: ${file.name} already exists (use --overwrite)`);
2466
2760
  continue;
2467
2761
  }
2468
- await fs9.ensureDir(targetDir);
2469
- const transformedContent = transformImports4(file.content, config);
2470
- await fs9.writeFile(targetPath, transformedContent);
2762
+ await fs10.ensureDir(targetDir);
2763
+ const transformedContent = transformToInstalled(file.content, config);
2764
+ await fs10.writeFile(targetPath, transformedContent);
2471
2765
  }
2472
2766
  installSpinner.succeed(`Installed ${chalk9.green(name)}`);
2473
2767
  successCount++;
@@ -2513,7 +2807,7 @@ ${installed.size} already installed, ${availableCount} available
2513
2807
 
2514
2808
  // src/index.ts
2515
2809
  var program = new Command9();
2516
- program.name("nativeui").description("Add beautiful UI components to your Expo/React Native project").version("0.0.1");
2810
+ program.name("mcellui").description("Add beautiful UI components to your Expo/React Native project").version("0.1.4");
2517
2811
  program.addCommand(initCommand);
2518
2812
  program.addCommand(addCommand);
2519
2813
  program.addCommand(listCommand);