@rootnative/cli 0.0.0-alpha.1 → 0.0.0-alpha.11
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 +45 -8
- package/dist/index.mjs +567 -160
- package/llms.txt +11 -3
- package/package.json +1 -1
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 =
|
|
52
|
-
const hasPackageJson = await
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
91
|
-
if (await
|
|
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 =
|
|
99
|
-
if (!await
|
|
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
|
|
104
|
-
const
|
|
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
|
|
227
|
+
import path6 from "path";
|
|
151
228
|
import { execa } from "execa";
|
|
152
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
328
|
-
const exists = await
|
|
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
|
|
461
|
+
await fs4.ensureDir(componentDir);
|
|
338
462
|
for (const filePath of entry.files) {
|
|
339
463
|
const content = await fetchFileContent(config, filePath);
|
|
340
|
-
const fileName =
|
|
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
|
|
347
|
-
|
|
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
|
|
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 =
|
|
379
|
-
await
|
|
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
|
|
400
|
-
|
|
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 =
|
|
531
|
+
const pkgPath = path6.resolve(cwd, "package.json");
|
|
408
532
|
let existingDeps = {};
|
|
409
533
|
try {
|
|
410
|
-
const pkg =
|
|
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
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
|
633
|
+
import path7 from "path";
|
|
505
634
|
import chalk3 from "chalk";
|
|
506
635
|
import { execa as execa2 } from "execa";
|
|
507
|
-
import
|
|
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
|
|
806
|
+
let nameInput;
|
|
632
807
|
if (name) {
|
|
633
|
-
|
|
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
|
-
|
|
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
|
-
|
|
689
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
1021
|
+
import path8 from "path";
|
|
793
1022
|
import chalk4 from "chalk";
|
|
794
|
-
import
|
|
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
|
-
|
|
1039
|
+
const initialized = await configExists(cwd);
|
|
1040
|
+
if (initialized) {
|
|
805
1041
|
logCheck("pass", "rootnative.json found");
|
|
806
1042
|
} else {
|
|
807
|
-
logCheck(
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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 =
|
|
825
|
-
if (await
|
|
826
|
-
const pkg = await
|
|
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 =
|
|
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
|
|
847
|
-
const corePkg = await
|
|
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
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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 (
|
|
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
|
-
"
|
|
887
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
899
|
-
|
|
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
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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
|
|
915
|
-
|
|
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
|
|
927
|
-
|
|
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
|
|
1443
|
+
import path10 from "path";
|
|
1069
1444
|
import chalk6 from "chalk";
|
|
1070
|
-
import
|
|
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 =
|
|
1097
|
-
const installed = await
|
|
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
|
|
1492
|
+
import path11 from "path";
|
|
1118
1493
|
import chalk8 from "chalk";
|
|
1119
|
-
import
|
|
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 =
|
|
1270
|
-
if (!await
|
|
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 =
|
|
1294
|
-
if (!await
|
|
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 =
|
|
1305
|
-
const localPath =
|
|
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
|
|
1314
|
-
localContent = await
|
|
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 =
|
|
1326
|
-
const localPath =
|
|
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
|
|
1330
|
-
localContent = await
|
|
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 =
|
|
1717
|
+
const barrelPath = path11.join(libDir, "rootnative-utils.ts");
|
|
1343
1718
|
let oldBarrel = "";
|
|
1344
|
-
if (await
|
|
1345
|
-
oldBarrel = await
|
|
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 =
|
|
1409
|
-
await
|
|
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 =
|
|
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
|
|
1424
|
-
|
|
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
|
|
1435
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
1835
|
+
if (!await fs9.pathExists(componentsDir)) {
|
|
1461
1836
|
return [];
|
|
1462
1837
|
}
|
|
1463
|
-
const entries = await
|
|
1838
|
+
const entries = await fs9.readdir(componentsDir);
|
|
1464
1839
|
const components = [];
|
|
1465
1840
|
for (const entry of entries) {
|
|
1466
|
-
const fullPath =
|
|
1467
|
-
const stat = await
|
|
1841
|
+
const fullPath = path11.join(componentsDir, entry);
|
|
1842
|
+
const stat = await fs9.stat(fullPath);
|
|
1468
1843
|
if (stat.isDirectory()) {
|
|
1469
|
-
const indexPath =
|
|
1470
|
-
if (await
|
|
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
|
|
1854
|
+
import path12 from "path";
|
|
1480
1855
|
import chalk9 from "chalk";
|
|
1481
1856
|
import { execa as execa4 } from "execa";
|
|
1482
|
-
import
|
|
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 =
|
|
1870
|
+
const pkgPath = path12.resolve(
|
|
1496
1871
|
cwd,
|
|
1497
1872
|
"node_modules",
|
|
1498
1873
|
...packageName.split("/"),
|
|
1499
1874
|
"package.json"
|
|
1500
1875
|
);
|
|
1501
|
-
if (!
|
|
1876
|
+
if (!fs10.pathExistsSync(pkgPath)) {
|
|
1502
1877
|
return null;
|
|
1503
1878
|
}
|
|
1504
|
-
const pkg =
|
|
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 =
|
|
1910
|
+
const pkgPath = path12.resolve(cwd, "package.json");
|
|
1536
1911
|
try {
|
|
1537
|
-
const pkg =
|
|
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(
|
|
1710
|
-
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(
|
|
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) {
|