@rootnative/cli 0.0.0-alpha.5 → 0.0.0-alpha.8

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/README.md CHANGED
@@ -1,3 +1,6 @@
1
+ <!-- Absolute URL: npm does not resolve repository-relative image paths. -->
2
+ <img src="https://raw.githubusercontent.com/rootnative/ui/main/assets/brand/rootnative-mark.png" alt="" width="88" height="88" />
3
+
1
4
  # rootnative
2
5
 
3
6
  CLI for adding [RootNative UI](https://github.com/rootnative/ui) components directly into your React Native or Expo project. Inspired by [shadcn/ui](https://ui.shadcn.com) — you own the code.
package/dist/index.mjs CHANGED
@@ -224,7 +224,7 @@ function getInstallCommand(pm, packages) {
224
224
  }
225
225
 
226
226
  // src/lib/installer.ts
227
- import path4 from "path";
227
+ import path6 from "path";
228
228
  import { execa } from "execa";
229
229
  import fs4 from "fs-extra";
230
230
 
@@ -289,6 +289,7 @@ async function fetchFileContent(config, filePath) {
289
289
  }
290
290
 
291
291
  // src/lib/resolver.ts
292
+ import path4 from "path";
292
293
  async function resolveComponents(config, requestedNames) {
293
294
  const resolved = /* @__PURE__ */ new Map();
294
295
  const utilsRegistry = await fetchUtilsRegistry(config);
@@ -321,9 +322,11 @@ function buildResult(resolved, utilsRegistry) {
321
322
  optionalDeps[pkg] = version;
322
323
  }
323
324
  }
325
+ const utilFileNames = {};
324
326
  for (const utilName of utilSet) {
325
327
  const utilEntry = utilsRegistry[utilName];
326
328
  if (utilEntry) {
329
+ utilFileNames[utilName] = path4.basename(utilEntry.file);
327
330
  for (const [pkg, version] of Object.entries(utilEntry.dependencies)) {
328
331
  optionalDeps[pkg] = version;
329
332
  }
@@ -332,6 +335,7 @@ function buildResult(resolved, utilsRegistry) {
332
335
  return {
333
336
  components,
334
337
  utils: Array.from(utilSet).sort(),
338
+ utilFileNames,
335
339
  npmDependencies: npmDeps,
336
340
  optionalNpmDependencies: optionalDeps
337
341
  };
@@ -341,16 +345,44 @@ function getComponentNames(result) {
341
345
  }
342
346
 
343
347
  // src/lib/transform.ts
348
+ import path5 from "path";
344
349
  var SINGLE_LINE_IMPORT_REGEX = /((?:import|export)\s+(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+|[\w,\s]+)\s+from\s+)(['"])([^'"]+)\2/g;
345
350
  var MULTI_LINE_IMPORT_REGEX = /((?:import|export)\s+(?:type\s+)?\{[\s\S]*?\}\s+from\s+)(['"])([^'"]+)\2/g;
346
351
  var SHARED_ROOT_MODULES = /* @__PURE__ */ new Set(["safe-area"]);
352
+ function isPrefixAlias(alias) {
353
+ return !alias.startsWith(".");
354
+ }
355
+ function buildSpecifier(alias, rest, componentsAlias, componentName) {
356
+ if (isPrefixAlias(alias)) {
357
+ return `${alias}/${rest}`;
358
+ }
359
+ const fromDir = path5.posix.join(
360
+ toPosix(stripRelativePrefix(componentsAlias)),
361
+ componentName
362
+ );
363
+ const target = path5.posix.join(toPosix(stripRelativePrefix(alias)), rest);
364
+ const relative = path5.posix.relative(fromDir, target);
365
+ return relative.startsWith(".") ? relative : `./${relative}`;
366
+ }
367
+ function stripRelativePrefix(alias) {
368
+ return alias.replace(/^\.\//, "");
369
+ }
370
+ function toPosix(value) {
371
+ return value.replace(/\\/g, "/");
372
+ }
347
373
  function transformImports(source, options) {
348
- const { config, installedComponents } = options;
374
+ const { config, componentName, installedComponents } = options;
349
375
  const componentsAlias = config.aliases.components;
350
376
  const libAlias = config.aliases.lib;
351
377
  function rewriteImport(match, prefix, quote, importPath) {
352
378
  if (importPath === "@rootnative/utils") {
353
- return `${prefix}${quote}${libAlias}/rootnative-utils${quote}`;
379
+ const specifier = buildSpecifier(
380
+ libAlias,
381
+ "rootnative-utils",
382
+ componentsAlias,
383
+ componentName
384
+ );
385
+ return `${prefix}${quote}${specifier}${quote}`;
354
386
  }
355
387
  if (importPath.startsWith("@rootnative/core")) {
356
388
  return match;
@@ -367,7 +399,13 @@ function transformImports(source, options) {
367
399
  const targetComponent = extractComponentName(importPath);
368
400
  if (targetComponent && installedComponents.includes(targetComponent)) {
369
401
  const restOfPath = importPath.replace(/^\.\.\//, "");
370
- return `${prefix}${quote}${componentsAlias}/${restOfPath}${quote}`;
402
+ const specifier = buildSpecifier(
403
+ componentsAlias,
404
+ restOfPath,
405
+ componentsAlias,
406
+ componentName
407
+ );
408
+ return `${prefix}${quote}${specifier}${quote}`;
371
409
  }
372
410
  }
373
411
  return match;
@@ -410,7 +448,7 @@ async function installComponents(options) {
410
448
  await generateBarrel(resolution, utilsRegistry, libDir);
411
449
  spinner.succeed("Utility files copied");
412
450
  for (const { entry } of resolution.components) {
413
- const componentDir = path4.join(componentsDir, entry.name);
451
+ const componentDir = path6.join(componentsDir, entry.name);
414
452
  const exists = await fs4.pathExists(componentDir);
415
453
  if (exists && !force) {
416
454
  logger.warn(
@@ -423,14 +461,14 @@ async function installComponents(options) {
423
461
  await fs4.ensureDir(componentDir);
424
462
  for (const filePath of entry.files) {
425
463
  const content = await fetchFileContent(config, filePath);
426
- const fileName = path4.basename(filePath);
464
+ const fileName = path6.basename(filePath);
427
465
  const transformed = transformImports(content, {
428
466
  config,
429
467
  componentName: entry.name,
430
468
  installedComponents: allComponentNames
431
469
  });
432
470
  await fs4.writeFile(
433
- path4.join(componentDir, fileName),
471
+ path6.join(componentDir, fileName),
434
472
  transformed,
435
473
  "utf-8"
436
474
  );
@@ -461,8 +499,8 @@ async function copyUtilFiles(config, resolution, utilsRegistry, libDir) {
461
499
  const utilEntry = utilsRegistry[utilName];
462
500
  if (!utilEntry) continue;
463
501
  const content = await fetchFileContent(config, utilEntry.file);
464
- const fileName = path4.basename(utilEntry.file);
465
- await fs4.writeFile(path4.join(libDir, fileName), content, "utf-8");
502
+ const fileName = path6.basename(utilEntry.file);
503
+ await fs4.writeFile(path6.join(libDir, fileName), content, "utf-8");
466
504
  }
467
505
  }
468
506
  async function generateBarrel(resolution, utilsRegistry, libDir) {
@@ -483,14 +521,14 @@ async function generateBarrel(resolution, utilsRegistry, libDir) {
483
521
  utilTypeExports
484
522
  );
485
523
  await fs4.writeFile(
486
- path4.join(libDir, "rootnative-utils.ts"),
524
+ path6.join(libDir, "rootnative-utils.ts"),
487
525
  barrelContent,
488
526
  "utf-8"
489
527
  );
490
528
  }
491
529
  function collectDepsToInstall(resolution, cwd) {
492
530
  const deps = [];
493
- const pkgPath = path4.resolve(cwd, "package.json");
531
+ const pkgPath = path6.resolve(cwd, "package.json");
494
532
  let existingDeps = {};
495
533
  try {
496
534
  const pkg = fs4.readJSONSync(pkgPath);
@@ -541,7 +579,10 @@ async function addCommand(componentNames, cwd, options) {
541
579
  if (resolution.utils.length > 0) {
542
580
  logger.break();
543
581
  console.log(chalk2.bold("Utilities to copy:"));
544
- console.log(` ${resolution.utils.map((u) => `${u}.ts`).join(", ")}`);
582
+ const fileNames = resolution.utils.map(
583
+ (u) => resolution.utilFileNames[u] ?? u
584
+ );
585
+ console.log(` ${fileNames.join(", ")}`);
545
586
  }
546
587
  const npmDeps = Object.keys(resolution.npmDependencies);
547
588
  const optionalDeps = Object.keys(resolution.optionalNpmDependencies);
@@ -589,7 +630,7 @@ async function addCommand(componentNames, cwd, options) {
589
630
  }
590
631
 
591
632
  // src/commands/create.ts
592
- import path5 from "path";
633
+ import path7 from "path";
593
634
  import chalk3 from "chalk";
594
635
  import { execa as execa2 } from "execa";
595
636
  import fs5 from "fs-extra";
@@ -656,6 +697,46 @@ function isValidTemplate(value) {
656
697
  function slugify(input) {
657
698
  return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
658
699
  }
700
+ function isCurrentDirName(input) {
701
+ const trimmed = input.trim();
702
+ return trimmed === "." || trimmed === "./" || trimmed === ".\\";
703
+ }
704
+ function resolveProjectTarget(input, cwd) {
705
+ const currentDir = path7.resolve(cwd);
706
+ if (isCurrentDirName(input)) {
707
+ const projectName2 = slugify(path7.basename(currentDir));
708
+ if (!projectName2) return { ok: false, reason: "invalid-folder-name" };
709
+ return {
710
+ ok: true,
711
+ target: { projectName: projectName2, targetDir: currentDir, useCurrentDir: true }
712
+ };
713
+ }
714
+ const projectName = slugify(input);
715
+ if (!projectName) return { ok: false, reason: "invalid-name" };
716
+ const targetDir = path7.resolve(currentDir, projectName);
717
+ return {
718
+ ok: true,
719
+ target: {
720
+ projectName,
721
+ targetDir,
722
+ useCurrentDir: targetDir === currentDir
723
+ }
724
+ };
725
+ }
726
+ function templateFiles(templateName) {
727
+ return [
728
+ ...TEMPLATE_CONFIGS[templateName].textFiles,
729
+ ...TEMPLATE_OPTIONAL_TEXT_FILES,
730
+ ...TEMPLATE_BINARY_FILES
731
+ ];
732
+ }
733
+ async function findConflicts(dir, files) {
734
+ const conflicts = [];
735
+ for (const file of files) {
736
+ if (await fs5.pathExists(path7.join(dir, file))) conflicts.push(file);
737
+ }
738
+ return conflicts;
739
+ }
659
740
  function toDisplayName(slug) {
660
741
  return slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
661
742
  }
@@ -722,9 +803,9 @@ async function createCommand(name, options = {}) {
722
803
  }
723
804
  templateName = value;
724
805
  }
725
- let projectName;
806
+ let nameInput;
726
807
  if (name) {
727
- projectName = slugify(name);
808
+ nameInput = name;
728
809
  } else {
729
810
  const { value } = await prompts2({
730
811
  type: "text",
@@ -737,8 +818,26 @@ async function createCommand(name, options = {}) {
737
818
  logger.info("Create cancelled.");
738
819
  return;
739
820
  }
740
- projectName = slugify(value);
821
+ nameInput = value;
822
+ }
823
+ const resolved = resolveProjectTarget(nameInput, process.cwd());
824
+ if (!resolved.ok) {
825
+ if (resolved.reason === "invalid-folder-name") {
826
+ logger.error(
827
+ `Cannot make a project name from the folder ${chalk3.bold(
828
+ path7.basename(path7.resolve(process.cwd()))
829
+ )}.`
830
+ );
831
+ logger.info(
832
+ `Give a name instead: ${chalk3.bold("npx rootnative create my-app")}`
833
+ );
834
+ } else {
835
+ logger.error(`${chalk3.bold(nameInput)} is not a usable project name.`);
836
+ logger.info("Use letters and numbers, for example my-app.");
837
+ }
838
+ process.exit(1);
741
839
  }
840
+ const { projectName, targetDir, useCurrentDir } = resolved.target;
742
841
  let displayName;
743
842
  if (options.yes) {
744
843
  displayName = toDisplayName(projectName);
@@ -779,8 +878,34 @@ async function createCommand(name, options = {}) {
779
878
  }
780
879
  packageManager = value;
781
880
  }
782
- const targetDir = path5.resolve(process.cwd(), projectName);
783
- if (await fs5.pathExists(targetDir)) {
881
+ if (useCurrentDir) {
882
+ const conflicts = await findConflicts(
883
+ targetDir,
884
+ templateFiles(templateName)
885
+ );
886
+ if (conflicts.length > 0) {
887
+ logger.warn("These files in the current directory will be overwritten:");
888
+ for (const file of conflicts) {
889
+ logger.info(` ${file}`);
890
+ }
891
+ logger.break();
892
+ if (options.yes) {
893
+ logger.error("Nothing was changed.");
894
+ logger.info("Move or delete these files, then run create again.");
895
+ process.exit(1);
896
+ }
897
+ const { overwrite } = await prompts2({
898
+ type: "confirm",
899
+ name: "overwrite",
900
+ message: "Overwrite them?",
901
+ initial: false
902
+ });
903
+ if (!overwrite) {
904
+ logger.info("Create cancelled.");
905
+ return;
906
+ }
907
+ }
908
+ } else if (await fs5.pathExists(targetDir)) {
784
909
  if (options.yes) {
785
910
  logger.warn(`Directory ${chalk3.bold(projectName)} already exists.`);
786
911
  process.exit(1);
@@ -788,7 +913,7 @@ async function createCommand(name, options = {}) {
788
913
  const { overwrite } = await prompts2({
789
914
  type: "confirm",
790
915
  name: "overwrite",
791
- message: `Directory ${chalk3.bold(projectName)} already exists. Overwrite?`,
916
+ message: `Directory ${chalk3.bold(projectName)} already exists. Delete it and all of its contents?`,
792
917
  initial: false
793
918
  });
794
919
  if (!overwrite) {
@@ -804,7 +929,7 @@ async function createCommand(name, options = {}) {
804
929
  spinner.start();
805
930
  try {
806
931
  for (const dir of templateConfig.dirs) {
807
- await fs5.ensureDir(path5.join(targetDir, dir));
932
+ await fs5.ensureDir(path7.join(targetDir, dir));
808
933
  }
809
934
  for (const file of templateConfig.textFiles) {
810
935
  let content = await fetchText(`${templateBaseUrl}/${file}`);
@@ -829,18 +954,18 @@ async function createCommand(name, options = {}) {
829
954
  }
830
955
  content = JSON.stringify(appJson, null, 2) + "\n";
831
956
  }
832
- await fs5.outputFile(path5.join(targetDir, file), content);
957
+ await fs5.outputFile(path7.join(targetDir, file), content);
833
958
  }
834
959
  for (const file of TEMPLATE_OPTIONAL_TEXT_FILES) {
835
960
  const content = await fetchTextOptional(`${templateBaseUrl}/${file}`);
836
961
  if (content !== null) {
837
- await fs5.outputFile(path5.join(targetDir, file), content);
962
+ await fs5.outputFile(path7.join(targetDir, file), content);
838
963
  }
839
964
  }
840
965
  for (const file of TEMPLATE_BINARY_FILES) {
841
966
  const buffer = await fetchBinary(`${templateBaseUrl}/${file}`);
842
967
  if (buffer) {
843
- await fs5.outputFile(path5.join(targetDir, file), buffer);
968
+ await fs5.outputFile(path7.join(targetDir, file), buffer);
844
969
  }
845
970
  }
846
971
  spinner.succeed("Project created");
@@ -872,7 +997,9 @@ async function createCommand(name, options = {}) {
872
997
  logger.break();
873
998
  logger.error("Failed to install dependencies");
874
999
  logger.info(
875
- `Run manually: ${chalk3.bold(`cd ${projectName} && ${installCmd}`)}`
1000
+ `Run manually: ${chalk3.bold(
1001
+ useCurrentDir ? installCmd : `cd ${projectName} && ${installCmd}`
1002
+ )}`
876
1003
  );
877
1004
  }
878
1005
  }
@@ -880,7 +1007,9 @@ async function createCommand(name, options = {}) {
880
1007
  logger.success(`Project ${chalk3.bold(displayName)} is ready!`);
881
1008
  logger.break();
882
1009
  logger.info("Next steps:");
883
- logger.info(` cd ${projectName}`);
1010
+ if (!useCurrentDir) {
1011
+ logger.info(` cd ${projectName}`);
1012
+ }
884
1013
  if (!shouldInstall) {
885
1014
  logger.info(` ${getInstallCommand2(packageManager)}`);
886
1015
  }
@@ -889,7 +1018,7 @@ async function createCommand(name, options = {}) {
889
1018
  }
890
1019
 
891
1020
  // src/commands/doctor.ts
892
- import path6 from "path";
1021
+ import path8 from "path";
893
1022
  import chalk4 from "chalk";
894
1023
  import fs6 from "fs-extra";
895
1024
  function logCheck(status, message) {
@@ -927,7 +1056,7 @@ async function doctorCommand(cwd) {
927
1056
  logCheck("fail", "Not a React Native or Expo project");
928
1057
  issues++;
929
1058
  }
930
- const pkgPath = path6.resolve(cwd, "package.json");
1059
+ const pkgPath = path8.resolve(cwd, "package.json");
931
1060
  if (await fs6.pathExists(pkgPath)) {
932
1061
  const pkg = await fs6.readJSON(pkgPath);
933
1062
  const allDeps = {
@@ -942,7 +1071,7 @@ async function doctorCommand(cwd) {
942
1071
  issues++;
943
1072
  }
944
1073
  }
945
- const corePkgPath = path6.resolve(
1074
+ const corePkgPath = path8.resolve(
946
1075
  cwd,
947
1076
  "node_modules",
948
1077
  "@rootnative",
@@ -973,7 +1102,7 @@ async function doctorCommand(cwd) {
973
1102
  const dirs = await fs6.readdir(componentsDir);
974
1103
  const componentDirs = [];
975
1104
  for (const dir of dirs) {
976
- const fullPath = path6.join(componentsDir, dir);
1105
+ const fullPath = path8.join(componentsDir, dir);
977
1106
  const stat = await fs6.stat(fullPath);
978
1107
  if (stat.isDirectory()) {
979
1108
  componentDirs.push(dir);
@@ -982,7 +1111,7 @@ async function doctorCommand(cwd) {
982
1111
  if (componentDirs.length > 0) {
983
1112
  let integrityOk = true;
984
1113
  for (const dir of componentDirs) {
985
- const indexPath = path6.join(componentsDir, dir, "index.ts");
1114
+ const indexPath = path8.join(componentsDir, dir, "index.ts");
986
1115
  if (!await fs6.pathExists(indexPath)) {
987
1116
  logCheck("warn", `Component ${dir} is missing index.ts`);
988
1117
  integrityOk = false;
@@ -1007,7 +1136,7 @@ async function doctorCommand(cwd) {
1007
1136
  );
1008
1137
  }
1009
1138
  const libDir = resolveAliasPath(config.aliases.lib, cwd);
1010
- const barrelPath = path6.join(libDir, "rootnative-utils.ts");
1139
+ const barrelPath = path8.join(libDir, "rootnative-utils.ts");
1011
1140
  if (await fs6.pathExists(barrelPath)) {
1012
1141
  logCheck("pass", "Utility barrel file present");
1013
1142
  } else {
@@ -1019,8 +1148,8 @@ async function doctorCommand(cwd) {
1019
1148
  }
1020
1149
  }
1021
1150
  }
1022
- const nodeModules = path6.resolve(cwd, "node_modules");
1023
- const inertiaPkgPath = path6.join(
1151
+ const nodeModules = path8.resolve(cwd, "node_modules");
1152
+ const inertiaPkgPath = path8.join(
1024
1153
  nodeModules,
1025
1154
  "@rootnative",
1026
1155
  "inertia",
@@ -1037,7 +1166,7 @@ async function doctorCommand(cwd) {
1037
1166
  issues++;
1038
1167
  }
1039
1168
  for (const peer of REQUIRED_BARREL_PEERS) {
1040
- if (await fs6.pathExists(path6.join(nodeModules, peer.name))) {
1169
+ if (await fs6.pathExists(path8.join(nodeModules, peer.name))) {
1041
1170
  logCheck("pass", `${peer.name} installed`);
1042
1171
  continue;
1043
1172
  }
@@ -1046,7 +1175,7 @@ async function doctorCommand(cwd) {
1046
1175
  issues++;
1047
1176
  }
1048
1177
  const safeAreaInstalled = await fs6.pathExists(
1049
- path6.join(nodeModules, "react-native-safe-area-context")
1178
+ path8.join(nodeModules, "react-native-safe-area-context")
1050
1179
  );
1051
1180
  if (safeAreaInstalled) {
1052
1181
  logCheck("pass", "react-native-safe-area-context installed");
@@ -1057,7 +1186,7 @@ async function doctorCommand(cwd) {
1057
1186
  );
1058
1187
  }
1059
1188
  const vectorIconsInstalled = await fs6.pathExists(
1060
- path6.join(nodeModules, "@expo", "vector-icons")
1189
+ path8.join(nodeModules, "@expo", "vector-icons")
1061
1190
  );
1062
1191
  if (vectorIconsInstalled) {
1063
1192
  logCheck("pass", "@expo/vector-icons installed");
@@ -1083,7 +1212,7 @@ import { execa as execa3 } from "execa";
1083
1212
  import prompts3 from "prompts";
1084
1213
 
1085
1214
  // src/lib/llm-docs.ts
1086
- import path7 from "path";
1215
+ import path9 from "path";
1087
1216
  import fs7 from "fs-extra";
1088
1217
  var CLAUDE_MD = "CLAUDE.md";
1089
1218
  var POINTER_MARKER = "@rootnative/components/llms.txt";
@@ -1099,7 +1228,7 @@ This project uses RootNative UI (\`@rootnative/*\`). LLM-optimized docs:
1099
1228
  Prefer the \`node_modules\` copies \u2014 they match the installed version exactly.
1100
1229
  `;
1101
1230
  async function ensureLlmDocsPointer(cwd) {
1102
- const filePath = path7.join(cwd, CLAUDE_MD);
1231
+ const filePath = path9.join(cwd, CLAUDE_MD);
1103
1232
  if (!await fs7.pathExists(filePath)) {
1104
1233
  await fs7.outputFile(filePath, `# CLAUDE.md
1105
1234
 
@@ -1311,7 +1440,7 @@ async function initCommand(cwd, options = {}) {
1311
1440
  }
1312
1441
 
1313
1442
  // src/commands/list.ts
1314
- import path8 from "path";
1443
+ import path10 from "path";
1315
1444
  import chalk6 from "chalk";
1316
1445
  import fs8 from "fs-extra";
1317
1446
  async function listCommand(cwd) {
@@ -1339,7 +1468,7 @@ async function listCommand(cwd) {
1339
1468
  ` ${chalk6.dim("-".repeat(70))}`
1340
1469
  );
1341
1470
  for (const component of registryIndex.components) {
1342
- const componentDir = path8.join(componentsDir, component.name);
1471
+ const componentDir = path10.join(componentsDir, component.name);
1343
1472
  const installed = await fs8.pathExists(componentDir);
1344
1473
  const status = installed ? chalk6.green("installed") : chalk6.dim("-");
1345
1474
  const nameDisplay = installed ? chalk6.green(component.name) : component.name;
@@ -1360,7 +1489,7 @@ function padEnd(str, length) {
1360
1489
  }
1361
1490
 
1362
1491
  // src/commands/update.ts
1363
- import path9 from "path";
1492
+ import path11 from "path";
1364
1493
  import chalk8 from "chalk";
1365
1494
  import fs9 from "fs-extra";
1366
1495
  import prompts4 from "prompts";
@@ -1512,7 +1641,7 @@ async function updateCommand(componentNames, cwd, options) {
1512
1641
  targetNames = componentNames;
1513
1642
  const missing = [];
1514
1643
  for (const name of targetNames) {
1515
- const dir = path9.join(componentsDir, name);
1644
+ const dir = path11.join(componentsDir, name);
1516
1645
  if (!await fs9.pathExists(dir)) {
1517
1646
  missing.push(name);
1518
1647
  }
@@ -1536,7 +1665,7 @@ async function updateCommand(componentNames, cwd, options) {
1536
1665
  resolveSpinner.succeed("Registry fetched");
1537
1666
  const componentDiffs = [];
1538
1667
  for (const { entry } of resolution.components) {
1539
- const componentDir = path9.join(componentsDir, entry.name);
1668
+ const componentDir = path11.join(componentsDir, entry.name);
1540
1669
  if (!await fs9.pathExists(componentDir)) {
1541
1670
  componentDiffs.push({
1542
1671
  name: entry.name,
@@ -1547,8 +1676,8 @@ async function updateCommand(componentNames, cwd, options) {
1547
1676
  }
1548
1677
  const diffs = [];
1549
1678
  for (const filePath of entry.files) {
1550
- const fileName = path9.basename(filePath);
1551
- const localPath = path9.join(componentDir, fileName);
1679
+ const fileName = path11.basename(filePath);
1680
+ const localPath = path11.join(componentDir, fileName);
1552
1681
  const remoteContent = await fetchFileContent(config, filePath);
1553
1682
  const transformed = transformImports(remoteContent, {
1554
1683
  config,
@@ -1568,8 +1697,8 @@ async function updateCommand(componentNames, cwd, options) {
1568
1697
  for (const utilName of resolution.utils) {
1569
1698
  const utilEntry = utilsRegistry[utilName];
1570
1699
  if (!utilEntry) continue;
1571
- const fileName = path9.basename(utilEntry.file);
1572
- const localPath = path9.join(libDir, fileName);
1700
+ const fileName = path11.basename(utilEntry.file);
1701
+ const localPath = path11.join(libDir, fileName);
1573
1702
  const remoteContent = await fetchFileContent(config, utilEntry.file);
1574
1703
  let localContent = "";
1575
1704
  if (await fs9.pathExists(localPath)) {
@@ -1585,7 +1714,7 @@ async function updateCommand(componentNames, cwd, options) {
1585
1714
  }
1586
1715
  }
1587
1716
  const newBarrel = generateUtilsBarrel(resolution.utils, utilExports);
1588
- const barrelPath = path9.join(libDir, "rootnative-utils.ts");
1717
+ const barrelPath = path11.join(libDir, "rootnative-utils.ts");
1589
1718
  let oldBarrel = "";
1590
1719
  if (await fs9.pathExists(barrelPath)) {
1591
1720
  oldBarrel = await fs9.readFile(barrelPath, "utf-8");
@@ -1651,7 +1780,7 @@ async function updateCommand(componentNames, cwd, options) {
1651
1780
  const applySpinner = createSpinner("Applying updates...");
1652
1781
  applySpinner.start();
1653
1782
  for (const comp of changedComponents) {
1654
- const componentDir = path9.join(componentsDir, comp.name);
1783
+ const componentDir = path11.join(componentsDir, comp.name);
1655
1784
  await fs9.ensureDir(componentDir);
1656
1785
  if (comp.diffs.length === 0) {
1657
1786
  const entry = resolution.components.find(
@@ -1660,14 +1789,14 @@ async function updateCommand(componentNames, cwd, options) {
1660
1789
  if (!entry) continue;
1661
1790
  for (const filePath of entry.entry.files) {
1662
1791
  const content = await fetchFileContent(config, filePath);
1663
- const fileName = path9.basename(filePath);
1792
+ const fileName = path11.basename(filePath);
1664
1793
  const transformed = transformImports(content, {
1665
1794
  config,
1666
1795
  componentName: comp.name,
1667
1796
  installedComponents: allComponentNames
1668
1797
  });
1669
1798
  await fs9.writeFile(
1670
- path9.join(componentDir, fileName),
1799
+ path11.join(componentDir, fileName),
1671
1800
  transformed,
1672
1801
  "utf-8"
1673
1802
  );
@@ -1678,7 +1807,7 @@ async function updateCommand(componentNames, cwd, options) {
1678
1807
  if (!diff.hasChanges) continue;
1679
1808
  const newContent = diff.lines.filter((l) => l.type !== "remove").map((l) => l.content).join("\n");
1680
1809
  await fs9.writeFile(
1681
- path9.join(componentDir, diff.fileName),
1810
+ path11.join(componentDir, diff.fileName),
1682
1811
  newContent,
1683
1812
  "utf-8"
1684
1813
  );
@@ -1688,7 +1817,7 @@ async function updateCommand(componentNames, cwd, options) {
1688
1817
  for (const diff of changedUtils) {
1689
1818
  if (!diff.hasChanges) continue;
1690
1819
  const newContent = diff.lines.filter((l) => l.type !== "remove").map((l) => l.content).join("\n");
1691
- const filePath = path9.join(
1820
+ const filePath = path11.join(
1692
1821
  libDir,
1693
1822
  // diff.fileName is like "lib/color.ts" — strip the "lib/" prefix
1694
1823
  diff.fileName.replace(/^lib\//, "")
@@ -1709,10 +1838,10 @@ async function getInstalledComponents(componentsDir) {
1709
1838
  const entries = await fs9.readdir(componentsDir);
1710
1839
  const components = [];
1711
1840
  for (const entry of entries) {
1712
- const fullPath = path9.join(componentsDir, entry);
1841
+ const fullPath = path11.join(componentsDir, entry);
1713
1842
  const stat = await fs9.stat(fullPath);
1714
1843
  if (stat.isDirectory()) {
1715
- const indexPath = path9.join(fullPath, "index.ts");
1844
+ const indexPath = path11.join(fullPath, "index.ts");
1716
1845
  if (await fs9.pathExists(indexPath)) {
1717
1846
  components.push(entry);
1718
1847
  }
@@ -1722,7 +1851,7 @@ async function getInstalledComponents(componentsDir) {
1722
1851
  }
1723
1852
 
1724
1853
  // src/commands/upgrade.ts
1725
- import path10 from "path";
1854
+ import path12 from "path";
1726
1855
  import chalk9 from "chalk";
1727
1856
  import { execa as execa4 } from "execa";
1728
1857
  import fs10 from "fs-extra";
@@ -1738,7 +1867,7 @@ async function fetchLatestFromNpm(packageName) {
1738
1867
  return response.json();
1739
1868
  }
1740
1869
  function getInstalledPackageInfo(cwd, packageName) {
1741
- const pkgPath = path10.resolve(
1870
+ const pkgPath = path12.resolve(
1742
1871
  cwd,
1743
1872
  "node_modules",
1744
1873
  ...packageName.split("/"),
@@ -1778,7 +1907,7 @@ function diffPeerDeps(current, latest) {
1778
1907
  return { added, changed, removed };
1779
1908
  }
1780
1909
  function getProjectDeps(cwd) {
1781
- const pkgPath = path10.resolve(cwd, "package.json");
1910
+ const pkgPath = path12.resolve(cwd, "package.json");
1782
1911
  try {
1783
1912
  const pkg = fs10.readJSONSync(pkgPath);
1784
1913
  return {
@@ -1965,10 +2094,26 @@ function isValidPackageManager(value) {
1965
2094
  return PACKAGE_MANAGERS.includes(value);
1966
2095
  }
1967
2096
 
2097
+ // src/lib/version.ts
2098
+ import { createRequire } from "module";
2099
+ var UNKNOWN_VERSION = "0.0.0-unknown";
2100
+ var PACKAGE_JSON_CANDIDATES = ["../package.json", "../../package.json"];
2101
+ function getCliVersion() {
2102
+ const require2 = createRequire(import.meta.url);
2103
+ for (const candidate of PACKAGE_JSON_CANDIDATES) {
2104
+ try {
2105
+ const pkg = require2(candidate);
2106
+ if (pkg.name === "@rootnative/cli" && pkg.version) return pkg.version;
2107
+ } catch {
2108
+ }
2109
+ }
2110
+ return UNKNOWN_VERSION;
2111
+ }
2112
+
1968
2113
  // src/index.ts
1969
2114
  var program = new Command();
1970
- program.name("rootnative").description("Add RootNative UI components to your React Native project").version("0.1.0");
1971
- program.command("create").description("Create a new project with RootNative UI pre-configured").argument("[name]", "Project name").option("-y, --yes", "Skip prompts and use defaults", false).option("-t, --template <name>", "Template to use (blank, with-router)").option(
2115
+ program.name("rootnative").description("Add RootNative UI components to your React Native project").version(getCliVersion());
2116
+ program.command("create").description("Create a new project with RootNative UI pre-configured").argument("[name]", 'Project name, or "." to use the current directory').option("-y, --yes", "Skip prompts and use defaults", false).option("-t, --template <name>", "Template to use (blank, with-router)").option(
1972
2117
  "--package-manager <pm>",
1973
2118
  `Package manager to use (${PACKAGE_MANAGERS.join(", ")})`
1974
2119
  ).action(async (name, options) => {
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # @rootnative/cli — CLI for RootNative UI
2
2
 
3
- > Version: 0.0.0-alpha.5
3
+ > Version: 0.0.0-alpha.8
4
4
  > Binary: `rootnative`
5
5
  > Requirements: Node >=18
6
6
 
@@ -15,9 +15,12 @@ Create a new project with RootNative UI pre-configured. Fetches the quickstart t
15
15
  ```bash
16
16
  npx rootnative create # Interactive
17
17
  npx rootnative create my-app # With name
18
+ npx rootnative create . # Into the current directory, named after it
18
19
  npx rootnative create my-app -y # Non-interactive, accept defaults
19
20
  ```
20
21
 
22
+ Pass `.` to scaffold into the directory you are already in. The project name comes from that directory's name, and the directory is never deleted — the CLI lists any file the template would overwrite and asks first (with `-y` it stops and changes nothing). A named project goes into a new subdirectory, and the CLI asks before it deletes an existing one.
23
+
21
24
  Options:
22
25
  - `-y, --yes` — Skip prompts and use defaults
23
26
  - `-t, --template <name>` — Template to use (`blank`, `with-router`)
@@ -47,7 +50,7 @@ Creates `rootnative.json`:
47
50
  "$schema": "https://rootnative.github.io/ui/schema.json",
48
51
  "aliases": { "components": "@/components/ui", "lib": "@/lib" },
49
52
  "registryUrl": "https://raw.githubusercontent.com/rootnative/ui",
50
- "registryVersion": "v0.0.0-alpha.5"
53
+ "registryVersion": "v0.0.0-alpha.8"
51
54
  }
52
55
  ```
53
56
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootnative/cli",
3
- "version": "0.0.0-alpha.5",
3
+ "version": "0.0.0-alpha.8",
4
4
  "description": "CLI for adding RootNative UI components to your React Native project",
5
5
  "private": false,
6
6
  "bin": {