@rootnative/cli 0.0.0-alpha.1 → 0.0.0-alpha.10

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.mjs CHANGED
@@ -45,15 +45,96 @@ function resolveAliasPath(alias, cwd) {
45
45
  }
46
46
 
47
47
  // src/lib/detector.ts
48
+ import path3 from "path";
49
+ import fs3 from "fs-extra";
50
+
51
+ // src/lib/tsconfig-paths.ts
48
52
  import path2 from "path";
49
53
  import fs2 from "fs-extra";
54
+ function stripJsonComments(raw) {
55
+ let out = "";
56
+ let inString = false;
57
+ let escaped = false;
58
+ for (let i = 0; i < raw.length; i++) {
59
+ const char = raw[i];
60
+ if (inString) {
61
+ out += char;
62
+ if (escaped) {
63
+ escaped = false;
64
+ } else if (char === "\\") {
65
+ escaped = true;
66
+ } else if (char === '"') {
67
+ inString = false;
68
+ }
69
+ continue;
70
+ }
71
+ if (char === '"') {
72
+ inString = true;
73
+ out += char;
74
+ continue;
75
+ }
76
+ if (char === "/" && raw[i + 1] === "*") {
77
+ const end = raw.indexOf("*/", i + 2);
78
+ i = end === -1 ? raw.length : end + 1;
79
+ continue;
80
+ }
81
+ if (char === "/" && raw[i + 1] === "/") {
82
+ while (i < raw.length && raw[i] !== "\n") i++;
83
+ out += "\n";
84
+ continue;
85
+ }
86
+ out += char;
87
+ }
88
+ return out;
89
+ }
90
+ function parseAliasPrefix(alias) {
91
+ const match = /^([^./\\][^/\\]*)\//.exec(alias);
92
+ if (!match) return null;
93
+ return { prefix: match[1], target: "src" };
94
+ }
95
+ async function ensureTsconfigPaths(cwd, alias) {
96
+ const parsed = parseAliasPrefix(alias);
97
+ if (!parsed) return { status: "not-an-alias" };
98
+ const { prefix, target } = parsed;
99
+ const tsconfigPath = path2.resolve(cwd, "tsconfig.json");
100
+ if (!await fs2.pathExists(tsconfigPath)) {
101
+ return { status: "no-tsconfig" };
102
+ }
103
+ try {
104
+ const raw = await fs2.readFile(tsconfigPath, "utf-8");
105
+ const tsconfig = JSON.parse(stripJsonComments(raw));
106
+ const key = `${prefix}/*`;
107
+ const existing = tsconfig.compilerOptions?.paths;
108
+ if (existing && key in existing) {
109
+ return { status: "already-mapped", prefix };
110
+ }
111
+ const compilerOptions = tsconfig.compilerOptions ?? {};
112
+ tsconfig.compilerOptions = {
113
+ ...compilerOptions,
114
+ baseUrl: compilerOptions.baseUrl ?? ".",
115
+ paths: {
116
+ ...existing,
117
+ [key]: [`./${target}/*`]
118
+ }
119
+ };
120
+ await fs2.writeJSON(tsconfigPath, tsconfig, { spaces: 2 });
121
+ return { status: "added", prefix, target };
122
+ } catch (error) {
123
+ return {
124
+ status: "failed",
125
+ error: error instanceof Error ? error.message : String(error)
126
+ };
127
+ }
128
+ }
129
+
130
+ // src/lib/detector.ts
50
131
  async function detectProjectType(cwd) {
51
- const pkgPath = path2.resolve(cwd, "package.json");
52
- const hasPackageJson = await fs2.pathExists(pkgPath);
132
+ const pkgPath = path3.resolve(cwd, "package.json");
133
+ const hasPackageJson = await fs3.pathExists(pkgPath);
53
134
  if (!hasPackageJson) {
54
135
  return "unknown";
55
136
  }
56
- const pkg = await fs2.readJSON(pkgPath);
137
+ const pkg = await fs3.readJSON(pkgPath);
57
138
  const allDeps = {
58
139
  ...pkg.dependencies,
59
140
  ...pkg.devDependencies
@@ -75,37 +156,33 @@ async function detectPackageManager(cwd) {
75
156
  ["package-lock.json", "npm"]
76
157
  ];
77
158
  for (const [file, manager] of lockFiles) {
78
- if (await fs2.pathExists(path2.resolve(cwd, file))) {
159
+ if (await fs3.pathExists(path3.resolve(cwd, file))) {
79
160
  return manager;
80
161
  }
81
162
  }
82
163
  return "npm";
83
164
  }
84
165
  async function detectTypeScript(cwd) {
85
- return fs2.pathExists(path2.resolve(cwd, "tsconfig.json"));
166
+ return fs3.pathExists(path3.resolve(cwd, "tsconfig.json"));
86
167
  }
87
168
  async function detectSrcDir(cwd) {
88
169
  const candidates = ["src", "app"];
89
170
  for (const dir of candidates) {
90
- const dirPath = path2.resolve(cwd, dir);
91
- if (await fs2.pathExists(dirPath)) {
171
+ const dirPath = path3.resolve(cwd, dir);
172
+ if (await fs3.pathExists(dirPath)) {
92
173
  return dir;
93
174
  }
94
175
  }
95
176
  return null;
96
177
  }
97
178
  async function detectAliases(cwd) {
98
- const tsconfigPath = path2.resolve(cwd, "tsconfig.json");
99
- if (!await fs2.pathExists(tsconfigPath)) {
179
+ const tsconfigPath = path3.resolve(cwd, "tsconfig.json");
180
+ if (!await fs3.pathExists(tsconfigPath)) {
100
181
  return null;
101
182
  }
102
183
  try {
103
- const raw = await fs2.readFile(tsconfigPath, "utf-8");
104
- const stripped = raw.replace(
105
- /\/\*[\s\S]*?\*\/|\/\/.*/g,
106
- ""
107
- );
108
- const tsconfig = JSON.parse(stripped);
184
+ const raw = await fs3.readFile(tsconfigPath, "utf-8");
185
+ const tsconfig = JSON.parse(stripJsonComments(raw));
109
186
  const paths = tsconfig.compilerOptions?.paths;
110
187
  if (!paths) {
111
188
  return null;
@@ -147,9 +224,9 @@ function getInstallCommand(pm, packages) {
147
224
  }
148
225
 
149
226
  // src/lib/installer.ts
150
- import path3 from "path";
227
+ import path6 from "path";
151
228
  import { execa } from "execa";
152
- import fs3 from "fs-extra";
229
+ import fs4 from "fs-extra";
153
230
 
154
231
  // src/lib/logger.ts
155
232
  import chalk from "chalk";
@@ -212,6 +289,7 @@ async function fetchFileContent(config, filePath) {
212
289
  }
213
290
 
214
291
  // src/lib/resolver.ts
292
+ import path4 from "path";
215
293
  async function resolveComponents(config, requestedNames) {
216
294
  const resolved = /* @__PURE__ */ new Map();
217
295
  const utilsRegistry = await fetchUtilsRegistry(config);
@@ -244,9 +322,11 @@ function buildResult(resolved, utilsRegistry) {
244
322
  optionalDeps[pkg] = version;
245
323
  }
246
324
  }
325
+ const utilFileNames = {};
247
326
  for (const utilName of utilSet) {
248
327
  const utilEntry = utilsRegistry[utilName];
249
328
  if (utilEntry) {
329
+ utilFileNames[utilName] = path4.basename(utilEntry.file);
250
330
  for (const [pkg, version] of Object.entries(utilEntry.dependencies)) {
251
331
  optionalDeps[pkg] = version;
252
332
  }
@@ -255,6 +335,7 @@ function buildResult(resolved, utilsRegistry) {
255
335
  return {
256
336
  components,
257
337
  utils: Array.from(utilSet).sort(),
338
+ utilFileNames,
258
339
  npmDependencies: npmDeps,
259
340
  optionalNpmDependencies: optionalDeps
260
341
  };
@@ -264,24 +345,67 @@ function getComponentNames(result) {
264
345
  }
265
346
 
266
347
  // src/lib/transform.ts
348
+ import path5 from "path";
267
349
  var SINGLE_LINE_IMPORT_REGEX = /((?:import|export)\s+(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+|[\w,\s]+)\s+from\s+)(['"])([^'"]+)\2/g;
268
350
  var MULTI_LINE_IMPORT_REGEX = /((?:import|export)\s+(?:type\s+)?\{[\s\S]*?\}\s+from\s+)(['"])([^'"]+)\2/g;
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
+ }
269
373
  function transformImports(source, options) {
270
- const { config, installedComponents } = options;
374
+ const { config, componentName, installedComponents } = options;
271
375
  const componentsAlias = config.aliases.components;
272
376
  const libAlias = config.aliases.lib;
273
377
  function rewriteImport(match, prefix, quote, importPath) {
274
378
  if (importPath === "@rootnative/utils") {
275
- 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}`;
276
386
  }
277
387
  if (importPath.startsWith("@rootnative/core")) {
278
388
  return match;
279
389
  }
390
+ if (importPath.startsWith("../internal/")) {
391
+ const restOfPath = importPath.replace(/^\.\.\/internal\//, "");
392
+ return `${prefix}${quote}./${restOfPath}${quote}`;
393
+ }
394
+ if (SHARED_ROOT_MODULES.has(importPath.replace(/^\.\.\//, ""))) {
395
+ const restOfPath = importPath.replace(/^\.\.\//, "");
396
+ return `${prefix}${quote}./${restOfPath}${quote}`;
397
+ }
280
398
  if (importPath.startsWith("../")) {
281
399
  const targetComponent = extractComponentName(importPath);
282
400
  if (targetComponent && installedComponents.includes(targetComponent)) {
283
401
  const restOfPath = importPath.replace(/^\.\.\//, "");
284
- 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}`;
285
409
  }
286
410
  }
287
411
  return match;
@@ -324,8 +448,8 @@ async function installComponents(options) {
324
448
  await generateBarrel(resolution, utilsRegistry, libDir);
325
449
  spinner.succeed("Utility files copied");
326
450
  for (const { entry } of resolution.components) {
327
- const componentDir = path3.join(componentsDir, entry.name);
328
- const exists = await fs3.pathExists(componentDir);
451
+ const componentDir = path6.join(componentsDir, entry.name);
452
+ const exists = await fs4.pathExists(componentDir);
329
453
  if (exists && !force) {
330
454
  logger.warn(
331
455
  `${entry.name} already exists, skipping (use --force to overwrite)`
@@ -334,17 +458,17 @@ async function installComponents(options) {
334
458
  }
335
459
  const compSpinner = createSpinner(`Adding ${entry.name}...`);
336
460
  compSpinner.start();
337
- await fs3.ensureDir(componentDir);
461
+ await fs4.ensureDir(componentDir);
338
462
  for (const filePath of entry.files) {
339
463
  const content = await fetchFileContent(config, filePath);
340
- const fileName = path3.basename(filePath);
464
+ const fileName = path6.basename(filePath);
341
465
  const transformed = transformImports(content, {
342
466
  config,
343
467
  componentName: entry.name,
344
468
  installedComponents: allComponentNames
345
469
  });
346
- await fs3.writeFile(
347
- path3.join(componentDir, fileName),
470
+ await fs4.writeFile(
471
+ path6.join(componentDir, fileName),
348
472
  transformed,
349
473
  "utf-8"
350
474
  );
@@ -370,13 +494,13 @@ async function installComponents(options) {
370
494
  }
371
495
  }
372
496
  async function copyUtilFiles(config, resolution, utilsRegistry, libDir) {
373
- await fs3.ensureDir(libDir);
497
+ await fs4.ensureDir(libDir);
374
498
  for (const utilName of resolution.utils) {
375
499
  const utilEntry = utilsRegistry[utilName];
376
500
  if (!utilEntry) continue;
377
501
  const content = await fetchFileContent(config, utilEntry.file);
378
- const fileName = path3.basename(utilEntry.file);
379
- await fs3.writeFile(path3.join(libDir, fileName), content, "utf-8");
502
+ const fileName = path6.basename(utilEntry.file);
503
+ await fs4.writeFile(path6.join(libDir, fileName), content, "utf-8");
380
504
  }
381
505
  }
382
506
  async function generateBarrel(resolution, utilsRegistry, libDir) {
@@ -396,18 +520,18 @@ async function generateBarrel(resolution, utilsRegistry, libDir) {
396
520
  utilExports,
397
521
  utilTypeExports
398
522
  );
399
- await fs3.writeFile(
400
- path3.join(libDir, "rootnative-utils.ts"),
523
+ await fs4.writeFile(
524
+ path6.join(libDir, "rootnative-utils.ts"),
401
525
  barrelContent,
402
526
  "utf-8"
403
527
  );
404
528
  }
405
529
  function collectDepsToInstall(resolution, cwd) {
406
530
  const deps = [];
407
- const pkgPath = path3.resolve(cwd, "package.json");
531
+ const pkgPath = path6.resolve(cwd, "package.json");
408
532
  let existingDeps = {};
409
533
  try {
410
- const pkg = fs3.readJSONSync(pkgPath);
534
+ const pkg = fs4.readJSONSync(pkgPath);
411
535
  existingDeps = {
412
536
  ...pkg.dependencies,
413
537
  ...pkg.devDependencies
@@ -455,7 +579,10 @@ async function addCommand(componentNames, cwd, options) {
455
579
  if (resolution.utils.length > 0) {
456
580
  logger.break();
457
581
  console.log(chalk2.bold("Utilities to copy:"));
458
- 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(", ")}`);
459
586
  }
460
587
  const npmDeps = Object.keys(resolution.npmDependencies);
461
588
  const optionalDeps = Object.keys(resolution.optionalNpmDependencies);
@@ -474,15 +601,17 @@ async function addCommand(componentNames, cwd, options) {
474
601
  logger.info("Dry run complete. No files were written.");
475
602
  return;
476
603
  }
477
- const { proceed } = await prompts({
478
- type: "confirm",
479
- name: "proceed",
480
- message: "Proceed with installation?",
481
- initial: true
482
- });
483
- if (!proceed) {
484
- logger.info("Cancelled.");
485
- return;
604
+ if (!options.yes) {
605
+ const { proceed } = await prompts({
606
+ type: "confirm",
607
+ name: "proceed",
608
+ message: "Proceed with installation?",
609
+ initial: true
610
+ });
611
+ if (!proceed) {
612
+ logger.info("Cancelled.");
613
+ return;
614
+ }
486
615
  }
487
616
  const project = await detectProject(cwd);
488
617
  const pm = options.packageManager ?? project.packageManager;
@@ -501,10 +630,10 @@ async function addCommand(componentNames, cwd, options) {
501
630
  }
502
631
 
503
632
  // src/commands/create.ts
504
- import path4 from "path";
633
+ import path7 from "path";
505
634
  import chalk3 from "chalk";
506
635
  import { execa as execa2 } from "execa";
507
- import fs4 from "fs-extra";
636
+ import fs5 from "fs-extra";
508
637
  import prompts2 from "prompts";
509
638
  var NPM_REGISTRY = "https://registry.npmjs.org";
510
639
  var ROOTNATIVE_PACKAGES = ["@rootnative/core", "@rootnative/components"];
@@ -561,12 +690,53 @@ var TEMPLATE_BINARY_FILES = [
561
690
  "assets/adaptive-icon.png",
562
691
  "assets/favicon.png"
563
692
  ];
693
+ var TEMPLATE_OPTIONAL_TEXT_FILES = ["CLAUDE.md", "README.md"];
564
694
  function isValidTemplate(value) {
565
695
  return value in TEMPLATE_CONFIGS;
566
696
  }
567
697
  function slugify(input) {
568
698
  return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
569
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
+ }
570
740
  function toDisplayName(slug) {
571
741
  return slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
572
742
  }
@@ -589,6 +759,11 @@ async function fetchText(url) {
589
759
  }
590
760
  return res.text();
591
761
  }
762
+ async function fetchTextOptional(url) {
763
+ const res = await fetch(url);
764
+ if (!res.ok) return null;
765
+ return res.text();
766
+ }
592
767
  async function fetchBinary(url) {
593
768
  const res = await fetch(url);
594
769
  if (!res.ok) return null;
@@ -628,9 +803,9 @@ async function createCommand(name, options = {}) {
628
803
  }
629
804
  templateName = value;
630
805
  }
631
- let projectName;
806
+ let nameInput;
632
807
  if (name) {
633
- projectName = slugify(name);
808
+ nameInput = name;
634
809
  } else {
635
810
  const { value } = await prompts2({
636
811
  type: "text",
@@ -643,8 +818,26 @@ async function createCommand(name, options = {}) {
643
818
  logger.info("Create cancelled.");
644
819
  return;
645
820
  }
646
- 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);
647
839
  }
840
+ const { projectName, targetDir, useCurrentDir } = resolved.target;
648
841
  let displayName;
649
842
  if (options.yes) {
650
843
  displayName = toDisplayName(projectName);
@@ -685,8 +878,34 @@ async function createCommand(name, options = {}) {
685
878
  }
686
879
  packageManager = value;
687
880
  }
688
- const targetDir = path4.resolve(process.cwd(), projectName);
689
- if (await fs4.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)) {
690
909
  if (options.yes) {
691
910
  logger.warn(`Directory ${chalk3.bold(projectName)} already exists.`);
692
911
  process.exit(1);
@@ -694,14 +913,14 @@ async function createCommand(name, options = {}) {
694
913
  const { overwrite } = await prompts2({
695
914
  type: "confirm",
696
915
  name: "overwrite",
697
- message: `Directory ${chalk3.bold(projectName)} already exists. Overwrite?`,
916
+ message: `Directory ${chalk3.bold(projectName)} already exists. Delete it and all of its contents?`,
698
917
  initial: false
699
918
  });
700
919
  if (!overwrite) {
701
920
  logger.info("Create cancelled.");
702
921
  return;
703
922
  }
704
- await fs4.remove(targetDir);
923
+ await fs5.remove(targetDir);
705
924
  }
706
925
  const templateConfig = TEMPLATE_CONFIGS[templateName];
707
926
  const { baseUrl, pinnedVersion } = await resolveTemplateSource();
@@ -710,7 +929,7 @@ async function createCommand(name, options = {}) {
710
929
  spinner.start();
711
930
  try {
712
931
  for (const dir of templateConfig.dirs) {
713
- await fs4.ensureDir(path4.join(targetDir, dir));
932
+ await fs5.ensureDir(path7.join(targetDir, dir));
714
933
  }
715
934
  for (const file of templateConfig.textFiles) {
716
935
  let content = await fetchText(`${templateBaseUrl}/${file}`);
@@ -735,12 +954,18 @@ async function createCommand(name, options = {}) {
735
954
  }
736
955
  content = JSON.stringify(appJson, null, 2) + "\n";
737
956
  }
738
- await fs4.outputFile(path4.join(targetDir, file), content);
957
+ await fs5.outputFile(path7.join(targetDir, file), content);
958
+ }
959
+ for (const file of TEMPLATE_OPTIONAL_TEXT_FILES) {
960
+ const content = await fetchTextOptional(`${templateBaseUrl}/${file}`);
961
+ if (content !== null) {
962
+ await fs5.outputFile(path7.join(targetDir, file), content);
963
+ }
739
964
  }
740
965
  for (const file of TEMPLATE_BINARY_FILES) {
741
966
  const buffer = await fetchBinary(`${templateBaseUrl}/${file}`);
742
967
  if (buffer) {
743
- await fs4.outputFile(path4.join(targetDir, file), buffer);
968
+ await fs5.outputFile(path7.join(targetDir, file), buffer);
744
969
  }
745
970
  }
746
971
  spinner.succeed("Project created");
@@ -772,7 +997,9 @@ async function createCommand(name, options = {}) {
772
997
  logger.break();
773
998
  logger.error("Failed to install dependencies");
774
999
  logger.info(
775
- `Run manually: ${chalk3.bold(`cd ${projectName} && ${installCmd}`)}`
1000
+ `Run manually: ${chalk3.bold(
1001
+ useCurrentDir ? installCmd : `cd ${projectName} && ${installCmd}`
1002
+ )}`
776
1003
  );
777
1004
  }
778
1005
  }
@@ -780,7 +1007,9 @@ async function createCommand(name, options = {}) {
780
1007
  logger.success(`Project ${chalk3.bold(displayName)} is ready!`);
781
1008
  logger.break();
782
1009
  logger.info("Next steps:");
783
- logger.info(` cd ${projectName}`);
1010
+ if (!useCurrentDir) {
1011
+ logger.info(` cd ${projectName}`);
1012
+ }
784
1013
  if (!shouldInstall) {
785
1014
  logger.info(` ${getInstallCommand2(packageManager)}`);
786
1015
  }
@@ -789,28 +1018,34 @@ async function createCommand(name, options = {}) {
789
1018
  }
790
1019
 
791
1020
  // src/commands/doctor.ts
792
- import path5 from "path";
1021
+ import path8 from "path";
793
1022
  import chalk4 from "chalk";
794
- import fs5 from "fs-extra";
1023
+ import fs6 from "fs-extra";
795
1024
  function logCheck(status, message) {
796
- const icon = status === "pass" ? chalk4.green("[pass]") : status === "warn" ? chalk4.yellow("[warn]") : chalk4.red("[fail]");
1025
+ const icon = status === "pass" ? chalk4.green("[pass]") : status === "warn" ? chalk4.yellow("[warn]") : status === "info" ? chalk4.cyan("[info]") : chalk4.red("[fail]");
797
1026
  console.log(` ${icon} ${message}`);
798
1027
  }
1028
+ var REQUIRED_BARREL_PEERS = [
1029
+ {
1030
+ name: "react-native-svg",
1031
+ reason: "the @rootnative/components barrel requires it at load time (circular-progress, loading-indicator)"
1032
+ }
1033
+ ];
799
1034
  async function doctorCommand(cwd) {
800
1035
  logger.break();
801
1036
  console.log(chalk4.bold("RootNative Doctor"));
802
1037
  logger.break();
803
1038
  let issues = 0;
804
- if (await configExists(cwd)) {
1039
+ const initialized = await configExists(cwd);
1040
+ if (initialized) {
805
1041
  logCheck("pass", "rootnative.json found");
806
1042
  } else {
807
- logCheck("fail", 'rootnative.json not found. Run "rootnative init" first.');
808
- issues++;
809
- logger.break();
810
- logger.error(`${issues} issue(s) found.`);
811
- return;
1043
+ logCheck(
1044
+ "info",
1045
+ 'Not initialized for the CLI workflow (no rootnative.json). Run "rootnative init" to copy component source.'
1046
+ );
812
1047
  }
813
- const config = await readConfig(cwd);
1048
+ const config = initialized ? await readConfig(cwd) : null;
814
1049
  const project = await detectProject(cwd);
815
1050
  if (project.type !== "unknown") {
816
1051
  logCheck(
@@ -821,9 +1056,9 @@ async function doctorCommand(cwd) {
821
1056
  logCheck("fail", "Not a React Native or Expo project");
822
1057
  issues++;
823
1058
  }
824
- const pkgPath = path5.resolve(cwd, "package.json");
825
- if (await fs5.pathExists(pkgPath)) {
826
- const pkg = await fs5.readJSON(pkgPath);
1059
+ const pkgPath = path8.resolve(cwd, "package.json");
1060
+ if (await fs6.pathExists(pkgPath)) {
1061
+ const pkg = await fs6.readJSON(pkgPath);
827
1062
  const allDeps = {
828
1063
  ...pkg.dependencies,
829
1064
  ...pkg.devDependencies
@@ -836,15 +1071,15 @@ async function doctorCommand(cwd) {
836
1071
  issues++;
837
1072
  }
838
1073
  }
839
- const corePkgPath = path5.resolve(
1074
+ const corePkgPath = path8.resolve(
840
1075
  cwd,
841
1076
  "node_modules",
842
1077
  "@rootnative",
843
1078
  "core",
844
1079
  "package.json"
845
1080
  );
846
- if (await fs5.pathExists(corePkgPath)) {
847
- const corePkg = await fs5.readJSON(corePkgPath);
1081
+ if (await fs6.pathExists(corePkgPath)) {
1082
+ const corePkg = await fs6.readJSON(corePkgPath);
848
1083
  logCheck("pass", `@rootnative/core@${corePkg.version} installed`);
849
1084
  } else {
850
1085
  logCheck(
@@ -861,70 +1096,97 @@ async function doctorCommand(cwd) {
861
1096
  "TypeScript not detected. RootNative components use TypeScript."
862
1097
  );
863
1098
  }
864
- const componentsDir = resolveAliasPath(config.aliases.components, cwd);
865
- if (await fs5.pathExists(componentsDir)) {
866
- const dirs = await fs5.readdir(componentsDir);
867
- const componentDirs = [];
868
- for (const dir of dirs) {
869
- const fullPath = path5.join(componentsDir, dir);
870
- const stat = await fs5.stat(fullPath);
871
- if (stat.isDirectory()) {
872
- componentDirs.push(dir);
873
- }
874
- }
875
- if (componentDirs.length > 0) {
876
- let integrityOk = true;
877
- for (const dir of componentDirs) {
878
- const indexPath = path5.join(componentsDir, dir, "index.ts");
879
- if (!await fs5.pathExists(indexPath)) {
880
- logCheck("warn", `Component ${dir} is missing index.ts`);
881
- integrityOk = false;
1099
+ if (config) {
1100
+ const componentsDir = resolveAliasPath(config.aliases.components, cwd);
1101
+ if (await fs6.pathExists(componentsDir)) {
1102
+ const dirs = await fs6.readdir(componentsDir);
1103
+ const componentDirs = [];
1104
+ for (const dir of dirs) {
1105
+ const fullPath = path8.join(componentsDir, dir);
1106
+ const stat = await fs6.stat(fullPath);
1107
+ if (stat.isDirectory()) {
1108
+ componentDirs.push(dir);
882
1109
  }
883
1110
  }
884
- if (integrityOk) {
1111
+ if (componentDirs.length > 0) {
1112
+ let integrityOk = true;
1113
+ for (const dir of componentDirs) {
1114
+ const indexPath = path8.join(componentsDir, dir, "index.ts");
1115
+ if (!await fs6.pathExists(indexPath)) {
1116
+ logCheck("warn", `Component ${dir} is missing index.ts`);
1117
+ integrityOk = false;
1118
+ }
1119
+ }
1120
+ if (integrityOk) {
1121
+ logCheck(
1122
+ "pass",
1123
+ `${componentDirs.length} component(s) installed, all files present`
1124
+ );
1125
+ }
1126
+ } else {
885
1127
  logCheck(
886
- "pass",
887
- `${componentDirs.length} component(s) installed, all files present`
1128
+ "warn",
1129
+ 'No components installed yet. Run "rootnative add <component>".'
888
1130
  );
889
1131
  }
890
1132
  } else {
891
1133
  logCheck(
892
1134
  "warn",
893
- 'No components installed yet. Run "rootnative add <component>".'
1135
+ `Components directory not found at ${config.aliases.components}`
894
1136
  );
895
1137
  }
1138
+ const libDir = resolveAliasPath(config.aliases.lib, cwd);
1139
+ const barrelPath = path8.join(libDir, "rootnative-utils.ts");
1140
+ if (await fs6.pathExists(barrelPath)) {
1141
+ logCheck("pass", "Utility barrel file present");
1142
+ } else {
1143
+ if (await fs6.pathExists(componentsDir)) {
1144
+ const dirs = await fs6.readdir(componentsDir);
1145
+ if (dirs.length > 0) {
1146
+ logCheck("warn", "Utility barrel file (rootnative-utils.ts) missing");
1147
+ }
1148
+ }
1149
+ }
1150
+ }
1151
+ const nodeModules = path8.resolve(cwd, "node_modules");
1152
+ const inertiaPkgPath = path8.join(
1153
+ nodeModules,
1154
+ "@rootnative",
1155
+ "inertia",
1156
+ "package.json"
1157
+ );
1158
+ if (await fs6.pathExists(inertiaPkgPath)) {
1159
+ const inertiaPkg = await fs6.readJSON(inertiaPkgPath);
1160
+ logCheck("pass", `@rootnative/inertia@${inertiaPkg.version} installed`);
896
1161
  } else {
897
1162
  logCheck(
898
- "warn",
899
- `Components directory not found at ${config.aliases.components}`
1163
+ "fail",
1164
+ '@rootnative/inertia not installed (required by all animated components). Run "rootnative upgrade" or install it with react-native-reanimated and react-native-worklets.'
900
1165
  );
1166
+ issues++;
901
1167
  }
902
- const libDir = resolveAliasPath(config.aliases.lib, cwd);
903
- const barrelPath = path5.join(libDir, "rootnative-utils.ts");
904
- if (await fs5.pathExists(barrelPath)) {
905
- logCheck("pass", "Utility barrel file present");
906
- } else {
907
- if (await fs5.pathExists(componentsDir)) {
908
- const dirs = await fs5.readdir(componentsDir);
909
- if (dirs.length > 0) {
910
- logCheck("warn", "Utility barrel file (rootnative-utils.ts) missing");
911
- }
1168
+ for (const peer of REQUIRED_BARREL_PEERS) {
1169
+ if (await fs6.pathExists(path8.join(nodeModules, peer.name))) {
1170
+ logCheck("pass", `${peer.name} installed`);
1171
+ continue;
912
1172
  }
1173
+ logCheck("fail", `${peer.name} is not installed \u2014 ${peer.reason}.`);
1174
+ console.log(` Run: ${chalk4.bold(`npx expo install ${peer.name}`)}`);
1175
+ issues++;
913
1176
  }
914
- const nodeModules = path5.resolve(cwd, "node_modules");
915
- const safeAreaInstalled = await fs5.pathExists(
916
- path5.join(nodeModules, "react-native-safe-area-context")
1177
+ const safeAreaInstalled = await fs6.pathExists(
1178
+ path8.join(nodeModules, "react-native-safe-area-context")
917
1179
  );
918
1180
  if (safeAreaInstalled) {
919
1181
  logCheck("pass", "react-native-safe-area-context installed");
920
1182
  } else {
921
1183
  logCheck(
922
1184
  "warn",
923
- "react-native-safe-area-context not installed (needed by: appbar, layout)"
1185
+ "react-native-safe-area-context not installed (needed by: appbar, layout, bottom-sheet, navigation-bar, snackbar)"
924
1186
  );
925
1187
  }
926
- const vectorIconsInstalled = await fs5.pathExists(
927
- path5.join(nodeModules, "@expo", "vector-icons")
1188
+ const vectorIconsInstalled = await fs6.pathExists(
1189
+ path8.join(nodeModules, "@expo", "vector-icons")
928
1190
  );
929
1191
  if (vectorIconsInstalled) {
930
1192
  logCheck("pass", "@expo/vector-icons installed");
@@ -937,6 +1199,7 @@ async function doctorCommand(cwd) {
937
1199
  logger.break();
938
1200
  if (issues > 0) {
939
1201
  logger.error(`${issues} issue(s) found.`);
1202
+ process.exitCode = 1;
940
1203
  } else {
941
1204
  logger.success("All checks passed!");
942
1205
  }
@@ -947,6 +1210,68 @@ async function doctorCommand(cwd) {
947
1210
  import chalk5 from "chalk";
948
1211
  import { execa as execa3 } from "execa";
949
1212
  import prompts3 from "prompts";
1213
+
1214
+ // src/lib/llm-docs.ts
1215
+ import path9 from "path";
1216
+ import fs7 from "fs-extra";
1217
+ var CLAUDE_MD = "CLAUDE.md";
1218
+ var POINTER_MARKER = "@rootnative/components/llms.txt";
1219
+ var LLM_DOCS_POINTER = `## RootNative UI docs for AI agents
1220
+
1221
+ This project uses RootNative UI (\`@rootnative/*\`). LLM-optimized docs:
1222
+
1223
+ - \`node_modules/@rootnative/components/llms.txt\` \u2014 all component props for the exact installed version (works offline)
1224
+ - \`node_modules/@rootnative/core/llms.txt\` \u2014 theme system API (\`ThemeProvider\`, \`useTheme\`, \`defineTheme\`)
1225
+ - https://rootnative.github.io/ui/llms.txt \u2014 hosted overview (latest release)
1226
+ - https://rootnative.github.io/ui/llms-full.txt \u2014 hosted complete API reference (latest release)
1227
+
1228
+ Prefer the \`node_modules\` copies \u2014 they match the installed version exactly.
1229
+ `;
1230
+ async function ensureLlmDocsPointer(cwd) {
1231
+ const filePath = path9.join(cwd, CLAUDE_MD);
1232
+ if (!await fs7.pathExists(filePath)) {
1233
+ await fs7.outputFile(filePath, `# CLAUDE.md
1234
+
1235
+ ${LLM_DOCS_POINTER}`);
1236
+ return "created";
1237
+ }
1238
+ const existing = await fs7.readFile(filePath, "utf8");
1239
+ if (existing.includes(POINTER_MARKER)) {
1240
+ return "already-present";
1241
+ }
1242
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
1243
+ await fs7.outputFile(filePath, `${existing}${separator}${LLM_DOCS_POINTER}`);
1244
+ return "appended";
1245
+ }
1246
+
1247
+ // src/lib/registry-version.ts
1248
+ var NPM_REGISTRY2 = "https://registry.npmjs.org";
1249
+ async function resolveRegistryVersion() {
1250
+ const fallback = "main";
1251
+ try {
1252
+ const res = await fetch(`${NPM_REGISTRY2}/@rootnative/core`);
1253
+ if (!res.ok) {
1254
+ return { version: fallback, fallback: { reason: "npm-unreachable" } };
1255
+ }
1256
+ const data = await res.json();
1257
+ const version = data["dist-tags"]?.latest;
1258
+ if (!version) {
1259
+ return { version: fallback, fallback: { reason: "no-latest-tag" } };
1260
+ }
1261
+ const ref = `v${version}`;
1262
+ const probe = await fetch(
1263
+ `${DEFAULT_CONFIG.registryUrl}/${ref}/registry/index.json`
1264
+ );
1265
+ if (!probe.ok) {
1266
+ return { version: fallback, fallback: { reason: "tag-missing", version } };
1267
+ }
1268
+ return { version: ref };
1269
+ } catch {
1270
+ return { version: fallback, fallback: { reason: "npm-unreachable" } };
1271
+ }
1272
+ }
1273
+
1274
+ // src/commands/init.ts
950
1275
  async function initCommand(cwd, options = {}) {
951
1276
  logger.break();
952
1277
  if (await configExists(cwd)) {
@@ -1020,8 +1345,10 @@ async function initCommand(cwd, options = {}) {
1020
1345
  componentsAlias = answers.componentsAlias;
1021
1346
  libAlias = answers.libAlias;
1022
1347
  }
1348
+ const registry = await resolveRegistryVersion();
1023
1349
  const config = {
1024
1350
  ...DEFAULT_CONFIG,
1351
+ registryVersion: registry.version,
1025
1352
  aliases: {
1026
1353
  components: componentsAlias,
1027
1354
  lib: libAlias
@@ -1029,6 +1356,54 @@ async function initCommand(cwd, options = {}) {
1029
1356
  };
1030
1357
  await writeConfig(cwd, config);
1031
1358
  logger.success("Created rootnative.json");
1359
+ if (registry.fallback) {
1360
+ logger.break();
1361
+ if (registry.fallback.reason === "tag-missing") {
1362
+ logger.warn(
1363
+ `Could not pin the registry to v${registry.fallback.version}; using "main".`
1364
+ );
1365
+ logger.warn(
1366
+ "Copied component source may not match your installed packages."
1367
+ );
1368
+ } else {
1369
+ logger.warn('Could not reach npm to pin the registry; using "main".');
1370
+ logger.warn(
1371
+ "Copied component source will come from the development branch."
1372
+ );
1373
+ }
1374
+ logger.break();
1375
+ }
1376
+ const tsconfigPatch = await ensureTsconfigPaths(cwd, componentsAlias);
1377
+ if (tsconfigPatch.status === "added") {
1378
+ logger.success(
1379
+ `Added "${tsconfigPatch.prefix}/*" path alias to tsconfig.json`
1380
+ );
1381
+ } else if (tsconfigPatch.status === "failed") {
1382
+ logger.warn(`Could not update tsconfig.json: ${tsconfigPatch.error}`);
1383
+ logger.info(
1384
+ `Add a "paths" mapping for "${componentsAlias.split("/")[0]}/*" by hand.`
1385
+ );
1386
+ }
1387
+ let addLlmDocs = options.yes;
1388
+ if (!options.yes) {
1389
+ const answer = await prompts3({
1390
+ type: "confirm",
1391
+ name: "addLlmDocs",
1392
+ message: "Add RootNative LLM docs pointer to CLAUDE.md (for AI agents)?",
1393
+ initial: true
1394
+ });
1395
+ addLlmDocs = answer.addLlmDocs;
1396
+ }
1397
+ if (addLlmDocs) {
1398
+ const result = await ensureLlmDocsPointer(cwd);
1399
+ if (result === "created") {
1400
+ logger.success("Created CLAUDE.md with LLM docs pointer");
1401
+ } else if (result === "appended") {
1402
+ logger.success("Added LLM docs pointer to CLAUDE.md");
1403
+ } else {
1404
+ logger.info("CLAUDE.md already points to the RootNative LLM docs");
1405
+ }
1406
+ }
1032
1407
  let installCore = options.yes;
1033
1408
  if (!options.yes) {
1034
1409
  const answer = await prompts3({
@@ -1065,9 +1440,9 @@ async function initCommand(cwd, options = {}) {
1065
1440
  }
1066
1441
 
1067
1442
  // src/commands/list.ts
1068
- import path6 from "path";
1443
+ import path10 from "path";
1069
1444
  import chalk6 from "chalk";
1070
- import fs6 from "fs-extra";
1445
+ import fs8 from "fs-extra";
1071
1446
  async function listCommand(cwd) {
1072
1447
  logger.break();
1073
1448
  const config = await readConfig(cwd);
@@ -1093,8 +1468,8 @@ async function listCommand(cwd) {
1093
1468
  ` ${chalk6.dim("-".repeat(70))}`
1094
1469
  );
1095
1470
  for (const component of registryIndex.components) {
1096
- const componentDir = path6.join(componentsDir, component.name);
1097
- const installed = await fs6.pathExists(componentDir);
1471
+ const componentDir = path10.join(componentsDir, component.name);
1472
+ const installed = await fs8.pathExists(componentDir);
1098
1473
  const status = installed ? chalk6.green("installed") : chalk6.dim("-");
1099
1474
  const nameDisplay = installed ? chalk6.green(component.name) : component.name;
1100
1475
  console.log(
@@ -1114,9 +1489,9 @@ function padEnd(str, length) {
1114
1489
  }
1115
1490
 
1116
1491
  // src/commands/update.ts
1117
- import path7 from "path";
1492
+ import path11 from "path";
1118
1493
  import chalk8 from "chalk";
1119
- import fs7 from "fs-extra";
1494
+ import fs9 from "fs-extra";
1120
1495
  import prompts4 from "prompts";
1121
1496
 
1122
1497
  // src/lib/diff.ts
@@ -1266,8 +1641,8 @@ async function updateCommand(componentNames, cwd, options) {
1266
1641
  targetNames = componentNames;
1267
1642
  const missing = [];
1268
1643
  for (const name of targetNames) {
1269
- const dir = path7.join(componentsDir, name);
1270
- if (!await fs7.pathExists(dir)) {
1644
+ const dir = path11.join(componentsDir, name);
1645
+ if (!await fs9.pathExists(dir)) {
1271
1646
  missing.push(name);
1272
1647
  }
1273
1648
  }
@@ -1290,8 +1665,8 @@ async function updateCommand(componentNames, cwd, options) {
1290
1665
  resolveSpinner.succeed("Registry fetched");
1291
1666
  const componentDiffs = [];
1292
1667
  for (const { entry } of resolution.components) {
1293
- const componentDir = path7.join(componentsDir, entry.name);
1294
- if (!await fs7.pathExists(componentDir)) {
1668
+ const componentDir = path11.join(componentsDir, entry.name);
1669
+ if (!await fs9.pathExists(componentDir)) {
1295
1670
  componentDiffs.push({
1296
1671
  name: entry.name,
1297
1672
  diffs: [],
@@ -1301,8 +1676,8 @@ async function updateCommand(componentNames, cwd, options) {
1301
1676
  }
1302
1677
  const diffs = [];
1303
1678
  for (const filePath of entry.files) {
1304
- const fileName = path7.basename(filePath);
1305
- const localPath = path7.join(componentDir, fileName);
1679
+ const fileName = path11.basename(filePath);
1680
+ const localPath = path11.join(componentDir, fileName);
1306
1681
  const remoteContent = await fetchFileContent(config, filePath);
1307
1682
  const transformed = transformImports(remoteContent, {
1308
1683
  config,
@@ -1310,8 +1685,8 @@ async function updateCommand(componentNames, cwd, options) {
1310
1685
  installedComponents: allComponentNames
1311
1686
  });
1312
1687
  let localContent = "";
1313
- if (await fs7.pathExists(localPath)) {
1314
- localContent = await fs7.readFile(localPath, "utf-8");
1688
+ if (await fs9.pathExists(localPath)) {
1689
+ localContent = await fs9.readFile(localPath, "utf-8");
1315
1690
  }
1316
1691
  diffs.push(computeDiff(localContent, transformed, fileName));
1317
1692
  }
@@ -1322,12 +1697,12 @@ async function updateCommand(componentNames, cwd, options) {
1322
1697
  for (const utilName of resolution.utils) {
1323
1698
  const utilEntry = utilsRegistry[utilName];
1324
1699
  if (!utilEntry) continue;
1325
- const fileName = path7.basename(utilEntry.file);
1326
- const localPath = path7.join(libDir, fileName);
1700
+ const fileName = path11.basename(utilEntry.file);
1701
+ const localPath = path11.join(libDir, fileName);
1327
1702
  const remoteContent = await fetchFileContent(config, utilEntry.file);
1328
1703
  let localContent = "";
1329
- if (await fs7.pathExists(localPath)) {
1330
- localContent = await fs7.readFile(localPath, "utf-8");
1704
+ if (await fs9.pathExists(localPath)) {
1705
+ localContent = await fs9.readFile(localPath, "utf-8");
1331
1706
  }
1332
1707
  utilDiffs.push(computeDiff(localContent, remoteContent, `lib/${fileName}`));
1333
1708
  }
@@ -1339,10 +1714,10 @@ async function updateCommand(componentNames, cwd, options) {
1339
1714
  }
1340
1715
  }
1341
1716
  const newBarrel = generateUtilsBarrel(resolution.utils, utilExports);
1342
- const barrelPath = path7.join(libDir, "rootnative-utils.ts");
1717
+ const barrelPath = path11.join(libDir, "rootnative-utils.ts");
1343
1718
  let oldBarrel = "";
1344
- if (await fs7.pathExists(barrelPath)) {
1345
- oldBarrel = await fs7.readFile(barrelPath, "utf-8");
1719
+ if (await fs9.pathExists(barrelPath)) {
1720
+ oldBarrel = await fs9.readFile(barrelPath, "utf-8");
1346
1721
  }
1347
1722
  utilDiffs.push(computeDiff(oldBarrel, newBarrel, "lib/rootnative-utils.ts"));
1348
1723
  const changedComponents = componentDiffs.filter((c) => c.hasChanges);
@@ -1405,8 +1780,8 @@ async function updateCommand(componentNames, cwd, options) {
1405
1780
  const applySpinner = createSpinner("Applying updates...");
1406
1781
  applySpinner.start();
1407
1782
  for (const comp of changedComponents) {
1408
- const componentDir = path7.join(componentsDir, comp.name);
1409
- await fs7.ensureDir(componentDir);
1783
+ const componentDir = path11.join(componentsDir, comp.name);
1784
+ await fs9.ensureDir(componentDir);
1410
1785
  if (comp.diffs.length === 0) {
1411
1786
  const entry = resolution.components.find(
1412
1787
  (c) => c.entry.name === comp.name
@@ -1414,14 +1789,14 @@ async function updateCommand(componentNames, cwd, options) {
1414
1789
  if (!entry) continue;
1415
1790
  for (const filePath of entry.entry.files) {
1416
1791
  const content = await fetchFileContent(config, filePath);
1417
- const fileName = path7.basename(filePath);
1792
+ const fileName = path11.basename(filePath);
1418
1793
  const transformed = transformImports(content, {
1419
1794
  config,
1420
1795
  componentName: comp.name,
1421
1796
  installedComponents: allComponentNames
1422
1797
  });
1423
- await fs7.writeFile(
1424
- path7.join(componentDir, fileName),
1798
+ await fs9.writeFile(
1799
+ path11.join(componentDir, fileName),
1425
1800
  transformed,
1426
1801
  "utf-8"
1427
1802
  );
@@ -1431,23 +1806,23 @@ async function updateCommand(componentNames, cwd, options) {
1431
1806
  for (const diff of comp.diffs) {
1432
1807
  if (!diff.hasChanges) continue;
1433
1808
  const newContent = diff.lines.filter((l) => l.type !== "remove").map((l) => l.content).join("\n");
1434
- await fs7.writeFile(
1435
- path7.join(componentDir, diff.fileName),
1809
+ await fs9.writeFile(
1810
+ path11.join(componentDir, diff.fileName),
1436
1811
  newContent,
1437
1812
  "utf-8"
1438
1813
  );
1439
1814
  }
1440
1815
  }
1441
- await fs7.ensureDir(libDir);
1816
+ await fs9.ensureDir(libDir);
1442
1817
  for (const diff of changedUtils) {
1443
1818
  if (!diff.hasChanges) continue;
1444
1819
  const newContent = diff.lines.filter((l) => l.type !== "remove").map((l) => l.content).join("\n");
1445
- const filePath = path7.join(
1820
+ const filePath = path11.join(
1446
1821
  libDir,
1447
1822
  // diff.fileName is like "lib/color.ts" — strip the "lib/" prefix
1448
1823
  diff.fileName.replace(/^lib\//, "")
1449
1824
  );
1450
- await fs7.writeFile(filePath, newContent, "utf-8");
1825
+ await fs9.writeFile(filePath, newContent, "utf-8");
1451
1826
  }
1452
1827
  applySpinner.succeed("Updates applied");
1453
1828
  logger.break();
@@ -1457,17 +1832,17 @@ async function updateCommand(componentNames, cwd, options) {
1457
1832
  logger.break();
1458
1833
  }
1459
1834
  async function getInstalledComponents(componentsDir) {
1460
- if (!await fs7.pathExists(componentsDir)) {
1835
+ if (!await fs9.pathExists(componentsDir)) {
1461
1836
  return [];
1462
1837
  }
1463
- const entries = await fs7.readdir(componentsDir);
1838
+ const entries = await fs9.readdir(componentsDir);
1464
1839
  const components = [];
1465
1840
  for (const entry of entries) {
1466
- const fullPath = path7.join(componentsDir, entry);
1467
- const stat = await fs7.stat(fullPath);
1841
+ const fullPath = path11.join(componentsDir, entry);
1842
+ const stat = await fs9.stat(fullPath);
1468
1843
  if (stat.isDirectory()) {
1469
- const indexPath = path7.join(fullPath, "index.ts");
1470
- if (await fs7.pathExists(indexPath)) {
1844
+ const indexPath = path11.join(fullPath, "index.ts");
1845
+ if (await fs9.pathExists(indexPath)) {
1471
1846
  components.push(entry);
1472
1847
  }
1473
1848
  }
@@ -1476,10 +1851,10 @@ async function getInstalledComponents(componentsDir) {
1476
1851
  }
1477
1852
 
1478
1853
  // src/commands/upgrade.ts
1479
- import path8 from "path";
1854
+ import path12 from "path";
1480
1855
  import chalk9 from "chalk";
1481
1856
  import { execa as execa4 } from "execa";
1482
- import fs8 from "fs-extra";
1857
+ import fs10 from "fs-extra";
1483
1858
  import prompts5 from "prompts";
1484
1859
  async function fetchLatestFromNpm(packageName) {
1485
1860
  const url = `https://registry.npmjs.org/${packageName}/latest`;
@@ -1492,16 +1867,16 @@ async function fetchLatestFromNpm(packageName) {
1492
1867
  return response.json();
1493
1868
  }
1494
1869
  function getInstalledPackageInfo(cwd, packageName) {
1495
- const pkgPath = path8.resolve(
1870
+ const pkgPath = path12.resolve(
1496
1871
  cwd,
1497
1872
  "node_modules",
1498
1873
  ...packageName.split("/"),
1499
1874
  "package.json"
1500
1875
  );
1501
- if (!fs8.pathExistsSync(pkgPath)) {
1876
+ if (!fs10.pathExistsSync(pkgPath)) {
1502
1877
  return null;
1503
1878
  }
1504
- const pkg = fs8.readJSONSync(pkgPath);
1879
+ const pkg = fs10.readJSONSync(pkgPath);
1505
1880
  return {
1506
1881
  version: pkg.version,
1507
1882
  peerDependencies: pkg.peerDependencies,
@@ -1532,9 +1907,9 @@ function diffPeerDeps(current, latest) {
1532
1907
  return { added, changed, removed };
1533
1908
  }
1534
1909
  function getProjectDeps(cwd) {
1535
- const pkgPath = path8.resolve(cwd, "package.json");
1910
+ const pkgPath = path12.resolve(cwd, "package.json");
1536
1911
  try {
1537
- const pkg = fs8.readJSONSync(pkgPath);
1912
+ const pkg = fs10.readJSONSync(pkgPath);
1538
1913
  return {
1539
1914
  ...pkg.dependencies,
1540
1915
  ...pkg.devDependencies
@@ -1546,6 +1921,19 @@ function getProjectDeps(cwd) {
1546
1921
  function hasDiffChanges(diff) {
1547
1922
  return Object.keys(diff.added).length > 0 || Object.keys(diff.changed).length > 0 || diff.removed.length > 0;
1548
1923
  }
1924
+ async function repinRegistryVersion(cwd) {
1925
+ const config = await readConfig(cwd);
1926
+ const next = await resolveRegistryVersion();
1927
+ if (next.fallback && config.registryVersion !== "main") {
1928
+ logger.warn(
1929
+ `Kept the registry pinned to ${chalk9.bold(config.registryVersion)} \u2014 could not resolve a newer release tag.`
1930
+ );
1931
+ return;
1932
+ }
1933
+ if (config.registryVersion === next.version) return;
1934
+ await writeConfig(cwd, { ...config, registryVersion: next.version });
1935
+ logger.success(`Registry pinned to ${chalk9.bold(next.version)}`);
1936
+ }
1549
1937
  async function upgradeCommand(cwd, options = {}) {
1550
1938
  logger.break();
1551
1939
  await readConfig(cwd);
@@ -1574,6 +1962,7 @@ async function upgradeCommand(cwd, options = {}) {
1574
1962
  `Already on the latest version ${chalk9.bold(`v${installed.version}`)}`
1575
1963
  );
1576
1964
  logger.break();
1965
+ await repinRegistryVersion(cwd);
1577
1966
  if (options.all) {
1578
1967
  logger.info("Updating installed components...");
1579
1968
  await updateCommand([], cwd, { all: true, dryRun: false });
@@ -1691,6 +2080,7 @@ async function upgradeCommand(cwd, options = {}) {
1691
2080
  logger.info(` ${pkg}`);
1692
2081
  }
1693
2082
  }
2083
+ await repinRegistryVersion(cwd);
1694
2084
  logger.break();
1695
2085
  if (options.all) {
1696
2086
  logger.info("Updating installed components...");
@@ -1704,10 +2094,26 @@ function isValidPackageManager(value) {
1704
2094
  return PACKAGE_MANAGERS.includes(value);
1705
2095
  }
1706
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
+
1707
2113
  // src/index.ts
1708
2114
  var program = new Command();
1709
- program.name("rootnative").description("Add RootNative UI components to your React Native project").version("0.1.0");
1710
- 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(
1711
2117
  "--package-manager <pm>",
1712
2118
  `Package manager to use (${PACKAGE_MANAGERS.join(", ")})`
1713
2119
  ).action(async (name, options) => {
@@ -1742,7 +2148,7 @@ program.command("init").description("Initialize your project for RootNative UI")
1742
2148
  handleError(error);
1743
2149
  }
1744
2150
  });
1745
- program.command("add").description("Add components to your project").argument("<components...>", "Component names to add").option("-f, --force", "Overwrite existing components", false).option(
2151
+ program.command("add").description("Add components to your project").argument("<components...>", "Component names to add").option("-y, --yes", "Skip prompts and use defaults", false).option("-f, --force", "Overwrite existing components", false).option(
1746
2152
  "-d, --dry-run",
1747
2153
  "Show what would be installed without making changes",
1748
2154
  false
@@ -1757,6 +2163,7 @@ program.command("add").description("Add components to your project").argument("<
1757
2163
  await addCommand(components, process.cwd(), {
1758
2164
  force: options.force,
1759
2165
  dryRun: options.dryRun,
2166
+ yes: options.yes,
1760
2167
  packageManager: options.packageManager
1761
2168
  });
1762
2169
  } catch (error) {