@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 +3 -0
- package/dist/index.mjs +205 -60
- package/llms.txt +5 -2
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
465
|
-
await fs4.writeFile(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
806
|
+
let nameInput;
|
|
726
807
|
if (name) {
|
|
727
|
-
|
|
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
|
-
|
|
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
|
-
|
|
783
|
-
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1023
|
-
const inertiaPkgPath =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1551
|
-
const localPath =
|
|
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 =
|
|
1572
|
-
const localPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
1841
|
+
const fullPath = path11.join(componentsDir, entry);
|
|
1713
1842
|
const stat = await fs9.stat(fullPath);
|
|
1714
1843
|
if (stat.isDirectory()) {
|
|
1715
|
-
const indexPath =
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
1971
|
-
program.command("create").description("Create a new project with RootNative UI pre-configured").argument("[name]",
|
|
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.
|
|
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.
|
|
53
|
+
"registryVersion": "v0.0.0-alpha.8"
|
|
51
54
|
}
|
|
52
55
|
```
|
|
53
56
|
|