@rootnative/cli 0.0.0-alpha.0
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/LICENSE +21 -0
- package/README.md +310 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1823 -0
- package/llms.txt +105 -0
- package/package.json +58 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1823 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/add.ts
|
|
7
|
+
import chalk2 from "chalk";
|
|
8
|
+
import prompts from "prompts";
|
|
9
|
+
|
|
10
|
+
// src/lib/config.ts
|
|
11
|
+
import path from "path";
|
|
12
|
+
import fs from "fs-extra";
|
|
13
|
+
var CONFIG_FILE = "rootnative.json";
|
|
14
|
+
var DEFAULT_CONFIG = {
|
|
15
|
+
$schema: "https://rootnative.github.io/ui/schema.json",
|
|
16
|
+
aliases: {
|
|
17
|
+
components: "@/components/ui",
|
|
18
|
+
lib: "@/lib"
|
|
19
|
+
},
|
|
20
|
+
registryUrl: "https://raw.githubusercontent.com/rootnative/ui",
|
|
21
|
+
registryVersion: "main"
|
|
22
|
+
};
|
|
23
|
+
function getConfigPath(cwd) {
|
|
24
|
+
return path.resolve(cwd, CONFIG_FILE);
|
|
25
|
+
}
|
|
26
|
+
async function configExists(cwd) {
|
|
27
|
+
return fs.pathExists(getConfigPath(cwd));
|
|
28
|
+
}
|
|
29
|
+
async function readConfig(cwd) {
|
|
30
|
+
const configPath = getConfigPath(cwd);
|
|
31
|
+
const exists = await fs.pathExists(configPath);
|
|
32
|
+
if (!exists) {
|
|
33
|
+
throw new Error('rootnative.json not found. Run "rootnative init" first.');
|
|
34
|
+
}
|
|
35
|
+
const raw = await fs.readJSON(configPath);
|
|
36
|
+
return raw;
|
|
37
|
+
}
|
|
38
|
+
async function writeConfig(cwd, config) {
|
|
39
|
+
const configPath = getConfigPath(cwd);
|
|
40
|
+
await fs.writeJSON(configPath, config, { spaces: 2 });
|
|
41
|
+
}
|
|
42
|
+
function resolveAliasPath(alias, cwd) {
|
|
43
|
+
const resolved = alias.replace(/^@\//, "src/");
|
|
44
|
+
return path.resolve(cwd, resolved);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/lib/detector.ts
|
|
48
|
+
import path2 from "path";
|
|
49
|
+
import fs2 from "fs-extra";
|
|
50
|
+
async function detectProjectType(cwd) {
|
|
51
|
+
const pkgPath = path2.resolve(cwd, "package.json");
|
|
52
|
+
const hasPackageJson = await fs2.pathExists(pkgPath);
|
|
53
|
+
if (!hasPackageJson) {
|
|
54
|
+
return "unknown";
|
|
55
|
+
}
|
|
56
|
+
const pkg = await fs2.readJSON(pkgPath);
|
|
57
|
+
const allDeps = {
|
|
58
|
+
...pkg.dependencies,
|
|
59
|
+
...pkg.devDependencies
|
|
60
|
+
};
|
|
61
|
+
if (allDeps.expo) {
|
|
62
|
+
return "expo";
|
|
63
|
+
}
|
|
64
|
+
if (allDeps["react-native"]) {
|
|
65
|
+
return "react-native";
|
|
66
|
+
}
|
|
67
|
+
return "unknown";
|
|
68
|
+
}
|
|
69
|
+
async function detectPackageManager(cwd) {
|
|
70
|
+
const lockFiles = [
|
|
71
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
72
|
+
["yarn.lock", "yarn"],
|
|
73
|
+
["bun.lockb", "bun"],
|
|
74
|
+
["bun.lock", "bun"],
|
|
75
|
+
["package-lock.json", "npm"]
|
|
76
|
+
];
|
|
77
|
+
for (const [file, manager] of lockFiles) {
|
|
78
|
+
if (await fs2.pathExists(path2.resolve(cwd, file))) {
|
|
79
|
+
return manager;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return "npm";
|
|
83
|
+
}
|
|
84
|
+
async function detectTypeScript(cwd) {
|
|
85
|
+
return fs2.pathExists(path2.resolve(cwd, "tsconfig.json"));
|
|
86
|
+
}
|
|
87
|
+
async function detectSrcDir(cwd) {
|
|
88
|
+
const candidates = ["src", "app"];
|
|
89
|
+
for (const dir of candidates) {
|
|
90
|
+
const dirPath = path2.resolve(cwd, dir);
|
|
91
|
+
if (await fs2.pathExists(dirPath)) {
|
|
92
|
+
return dir;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
async function detectAliases(cwd) {
|
|
98
|
+
const tsconfigPath = path2.resolve(cwd, "tsconfig.json");
|
|
99
|
+
if (!await fs2.pathExists(tsconfigPath)) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
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);
|
|
109
|
+
const paths = tsconfig.compilerOptions?.paths;
|
|
110
|
+
if (!paths) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const aliases = {};
|
|
114
|
+
for (const [alias, targets] of Object.entries(paths)) {
|
|
115
|
+
if (!Array.isArray(targets) || targets.length === 0) continue;
|
|
116
|
+
const cleanAlias = alias.replace(/\/\*$/, "");
|
|
117
|
+
const cleanTarget = targets[0].replace(/\/\*$/, "").replace(/^\.\//, "");
|
|
118
|
+
aliases[cleanAlias] = cleanTarget;
|
|
119
|
+
}
|
|
120
|
+
return Object.keys(aliases).length > 0 ? aliases : null;
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function detectProject(cwd) {
|
|
126
|
+
const [type, packageManager, hasTypeScript, srcDir, aliases] = await Promise.all([
|
|
127
|
+
detectProjectType(cwd),
|
|
128
|
+
detectPackageManager(cwd),
|
|
129
|
+
detectTypeScript(cwd),
|
|
130
|
+
detectSrcDir(cwd),
|
|
131
|
+
detectAliases(cwd)
|
|
132
|
+
]);
|
|
133
|
+
return { type, packageManager, hasTypeScript, srcDir, aliases };
|
|
134
|
+
}
|
|
135
|
+
function getInstallCommand(pm, packages) {
|
|
136
|
+
const pkgs = packages.join(" ");
|
|
137
|
+
switch (pm) {
|
|
138
|
+
case "pnpm":
|
|
139
|
+
return `pnpm add ${pkgs}`;
|
|
140
|
+
case "yarn":
|
|
141
|
+
return `yarn add ${pkgs}`;
|
|
142
|
+
case "bun":
|
|
143
|
+
return `bun add ${pkgs}`;
|
|
144
|
+
default:
|
|
145
|
+
return `npm install ${pkgs}`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/lib/installer.ts
|
|
150
|
+
import path3 from "path";
|
|
151
|
+
import { execa } from "execa";
|
|
152
|
+
import fs3 from "fs-extra";
|
|
153
|
+
|
|
154
|
+
// src/lib/logger.ts
|
|
155
|
+
import chalk from "chalk";
|
|
156
|
+
import ora from "ora";
|
|
157
|
+
var logger = {
|
|
158
|
+
info(message) {
|
|
159
|
+
console.log(chalk.cyan("info"), message);
|
|
160
|
+
},
|
|
161
|
+
success(message) {
|
|
162
|
+
console.log(chalk.green("success"), message);
|
|
163
|
+
},
|
|
164
|
+
warn(message) {
|
|
165
|
+
console.log(chalk.yellow("warn"), message);
|
|
166
|
+
},
|
|
167
|
+
error(message) {
|
|
168
|
+
console.log(chalk.red("error"), message);
|
|
169
|
+
},
|
|
170
|
+
break() {
|
|
171
|
+
console.log();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
function createSpinner(text) {
|
|
175
|
+
return ora({ text, color: "cyan" });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/lib/registry.ts
|
|
179
|
+
function buildBaseUrl(config) {
|
|
180
|
+
return `${config.registryUrl}/${config.registryVersion}`;
|
|
181
|
+
}
|
|
182
|
+
async function fetchJSON(url) {
|
|
183
|
+
const response = await fetch(url);
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Failed to fetch ${url}: ${response.status} ${response.statusText}`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return response.json();
|
|
190
|
+
}
|
|
191
|
+
async function fetchRegistryIndex(config) {
|
|
192
|
+
const url = `${buildBaseUrl(config)}/registry/index.json`;
|
|
193
|
+
return fetchJSON(url);
|
|
194
|
+
}
|
|
195
|
+
async function fetchComponentEntry(config, name) {
|
|
196
|
+
const url = `${buildBaseUrl(config)}/registry/components/${name}.json`;
|
|
197
|
+
return fetchJSON(url);
|
|
198
|
+
}
|
|
199
|
+
async function fetchUtilsRegistry(config) {
|
|
200
|
+
const url = `${buildBaseUrl(config)}/registry/utils.json`;
|
|
201
|
+
return fetchJSON(url);
|
|
202
|
+
}
|
|
203
|
+
async function fetchFileContent(config, filePath) {
|
|
204
|
+
const url = `${buildBaseUrl(config)}/${filePath}`;
|
|
205
|
+
const response = await fetch(url);
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`Failed to fetch file ${filePath}: ${response.status} ${response.statusText}`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return response.text();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/lib/resolver.ts
|
|
215
|
+
async function resolveComponents(config, requestedNames) {
|
|
216
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
217
|
+
const utilsRegistry = await fetchUtilsRegistry(config);
|
|
218
|
+
async function resolve(name, isDirect) {
|
|
219
|
+
if (resolved.has(name)) return;
|
|
220
|
+
const entry = await fetchComponentEntry(config, name);
|
|
221
|
+
resolved.set(name, { entry, isDirectRequest: isDirect });
|
|
222
|
+
for (const dep of entry.componentDependencies) {
|
|
223
|
+
await resolve(dep, false);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const name of requestedNames) {
|
|
227
|
+
await resolve(name, true);
|
|
228
|
+
}
|
|
229
|
+
return buildResult(resolved, utilsRegistry);
|
|
230
|
+
}
|
|
231
|
+
function buildResult(resolved, utilsRegistry) {
|
|
232
|
+
const components = Array.from(resolved.values());
|
|
233
|
+
const utilSet = /* @__PURE__ */ new Set();
|
|
234
|
+
const npmDeps = {};
|
|
235
|
+
const optionalDeps = {};
|
|
236
|
+
for (const { entry } of components) {
|
|
237
|
+
for (const util of entry.utils) {
|
|
238
|
+
utilSet.add(util);
|
|
239
|
+
}
|
|
240
|
+
for (const [pkg, version] of Object.entries(entry.dependencies)) {
|
|
241
|
+
npmDeps[pkg] = version;
|
|
242
|
+
}
|
|
243
|
+
for (const [pkg, version] of Object.entries(entry.optionalDependencies)) {
|
|
244
|
+
optionalDeps[pkg] = version;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const utilName of utilSet) {
|
|
248
|
+
const utilEntry = utilsRegistry[utilName];
|
|
249
|
+
if (utilEntry) {
|
|
250
|
+
for (const [pkg, version] of Object.entries(utilEntry.dependencies)) {
|
|
251
|
+
optionalDeps[pkg] = version;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
components,
|
|
257
|
+
utils: Array.from(utilSet).sort(),
|
|
258
|
+
npmDependencies: npmDeps,
|
|
259
|
+
optionalNpmDependencies: optionalDeps
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function getComponentNames(result) {
|
|
263
|
+
return result.components.map((c) => c.entry.name);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/lib/transform.ts
|
|
267
|
+
var SINGLE_LINE_IMPORT_REGEX = /((?:import|export)\s+(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+|[\w,\s]+)\s+from\s+)(['"])([^'"]+)\2/g;
|
|
268
|
+
var MULTI_LINE_IMPORT_REGEX = /((?:import|export)\s+(?:type\s+)?\{[\s\S]*?\}\s+from\s+)(['"])([^'"]+)\2/g;
|
|
269
|
+
function transformImports(source, options) {
|
|
270
|
+
const { config, installedComponents } = options;
|
|
271
|
+
const componentsAlias = config.aliases.components;
|
|
272
|
+
const libAlias = config.aliases.lib;
|
|
273
|
+
function rewriteImport(match, prefix, quote, importPath) {
|
|
274
|
+
if (importPath === "@rootnative/utils") {
|
|
275
|
+
return `${prefix}${quote}${libAlias}/rootnative-utils${quote}`;
|
|
276
|
+
}
|
|
277
|
+
if (importPath.startsWith("@rootnative/core")) {
|
|
278
|
+
return match;
|
|
279
|
+
}
|
|
280
|
+
if (importPath.startsWith("../")) {
|
|
281
|
+
const targetComponent = extractComponentName(importPath);
|
|
282
|
+
if (targetComponent && installedComponents.includes(targetComponent)) {
|
|
283
|
+
const restOfPath = importPath.replace(/^\.\.\//, "");
|
|
284
|
+
return `${prefix}${quote}${componentsAlias}/${restOfPath}${quote}`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return match;
|
|
288
|
+
}
|
|
289
|
+
let result = source.replace(MULTI_LINE_IMPORT_REGEX, rewriteImport);
|
|
290
|
+
result = result.replace(SINGLE_LINE_IMPORT_REGEX, rewriteImport);
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
function extractComponentName(importPath) {
|
|
294
|
+
const match = importPath.match(/^\.\.\/([^/]+)/);
|
|
295
|
+
return match ? match[1] : null;
|
|
296
|
+
}
|
|
297
|
+
function generateUtilsBarrel(utilNames, utilExports, utilTypeExports = {}) {
|
|
298
|
+
const lines = ["// Auto-generated by rootnative CLI. Do not edit."];
|
|
299
|
+
for (const utilName of utilNames.sort()) {
|
|
300
|
+
const exports = utilExports[utilName];
|
|
301
|
+
if (exports && exports.length > 0) {
|
|
302
|
+
lines.push(`export { ${exports.join(", ")} } from './${utilName}'`);
|
|
303
|
+
}
|
|
304
|
+
const typeExports = utilTypeExports[utilName];
|
|
305
|
+
if (typeExports && typeExports.length > 0) {
|
|
306
|
+
lines.push(
|
|
307
|
+
`export type { ${typeExports.join(", ")} } from './${utilName}'`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return lines.join("\n") + "\n";
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/lib/installer.ts
|
|
315
|
+
async function installComponents(options) {
|
|
316
|
+
const { config, cwd, resolution, packageManager, force } = options;
|
|
317
|
+
const componentsDir = resolveAliasPath(config.aliases.components, cwd);
|
|
318
|
+
const libDir = resolveAliasPath(config.aliases.lib, cwd);
|
|
319
|
+
const allComponentNames = getComponentNames(resolution);
|
|
320
|
+
const spinner = createSpinner("Copying utility files...");
|
|
321
|
+
spinner.start();
|
|
322
|
+
const utilsRegistry = await fetchUtilsRegistry(config);
|
|
323
|
+
await copyUtilFiles(config, resolution, utilsRegistry, libDir);
|
|
324
|
+
await generateBarrel(resolution, utilsRegistry, libDir);
|
|
325
|
+
spinner.succeed("Utility files copied");
|
|
326
|
+
for (const { entry } of resolution.components) {
|
|
327
|
+
const componentDir = path3.join(componentsDir, entry.name);
|
|
328
|
+
const exists = await fs3.pathExists(componentDir);
|
|
329
|
+
if (exists && !force) {
|
|
330
|
+
logger.warn(
|
|
331
|
+
`${entry.name} already exists, skipping (use --force to overwrite)`
|
|
332
|
+
);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const compSpinner = createSpinner(`Adding ${entry.name}...`);
|
|
336
|
+
compSpinner.start();
|
|
337
|
+
await fs3.ensureDir(componentDir);
|
|
338
|
+
for (const filePath of entry.files) {
|
|
339
|
+
const content = await fetchFileContent(config, filePath);
|
|
340
|
+
const fileName = path3.basename(filePath);
|
|
341
|
+
const transformed = transformImports(content, {
|
|
342
|
+
config,
|
|
343
|
+
componentName: entry.name,
|
|
344
|
+
installedComponents: allComponentNames
|
|
345
|
+
});
|
|
346
|
+
await fs3.writeFile(
|
|
347
|
+
path3.join(componentDir, fileName),
|
|
348
|
+
transformed,
|
|
349
|
+
"utf-8"
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
compSpinner.succeed(`Added ${entry.name}`);
|
|
353
|
+
}
|
|
354
|
+
const depsToInstall = collectDepsToInstall(resolution, cwd);
|
|
355
|
+
if (depsToInstall.length > 0) {
|
|
356
|
+
const command = getInstallCommand(packageManager, depsToInstall);
|
|
357
|
+
const [cmd, ...args] = command.split(" ");
|
|
358
|
+
logger.break();
|
|
359
|
+
logger.info("Installing dependencies...");
|
|
360
|
+
logger.break();
|
|
361
|
+
try {
|
|
362
|
+
await execa(cmd, args, { cwd, stdio: "inherit" });
|
|
363
|
+
logger.break();
|
|
364
|
+
logger.success("Dependencies installed");
|
|
365
|
+
} catch {
|
|
366
|
+
logger.break();
|
|
367
|
+
logger.error("Failed to install dependencies");
|
|
368
|
+
logger.error(`Run manually: ${command}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function copyUtilFiles(config, resolution, utilsRegistry, libDir) {
|
|
373
|
+
await fs3.ensureDir(libDir);
|
|
374
|
+
for (const utilName of resolution.utils) {
|
|
375
|
+
const utilEntry = utilsRegistry[utilName];
|
|
376
|
+
if (!utilEntry) continue;
|
|
377
|
+
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");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
async function generateBarrel(resolution, utilsRegistry, libDir) {
|
|
383
|
+
const utilExports = {};
|
|
384
|
+
const utilTypeExports = {};
|
|
385
|
+
for (const utilName of resolution.utils) {
|
|
386
|
+
const utilEntry = utilsRegistry[utilName];
|
|
387
|
+
if (utilEntry) {
|
|
388
|
+
utilExports[utilName] = utilEntry.exports;
|
|
389
|
+
if (utilEntry.typeExports) {
|
|
390
|
+
utilTypeExports[utilName] = utilEntry.typeExports;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const barrelContent = generateUtilsBarrel(
|
|
395
|
+
resolution.utils,
|
|
396
|
+
utilExports,
|
|
397
|
+
utilTypeExports
|
|
398
|
+
);
|
|
399
|
+
await fs3.writeFile(
|
|
400
|
+
path3.join(libDir, "rootnative-utils.ts"),
|
|
401
|
+
barrelContent,
|
|
402
|
+
"utf-8"
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
function collectDepsToInstall(resolution, cwd) {
|
|
406
|
+
const deps = [];
|
|
407
|
+
const pkgPath = path3.resolve(cwd, "package.json");
|
|
408
|
+
let existingDeps = {};
|
|
409
|
+
try {
|
|
410
|
+
const pkg = fs3.readJSONSync(pkgPath);
|
|
411
|
+
existingDeps = {
|
|
412
|
+
...pkg.dependencies,
|
|
413
|
+
...pkg.devDependencies
|
|
414
|
+
};
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
for (const [pkg] of Object.entries(resolution.npmDependencies)) {
|
|
418
|
+
if (!existingDeps[pkg]) {
|
|
419
|
+
deps.push(pkg);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return deps;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/commands/add.ts
|
|
426
|
+
async function addCommand(componentNames, cwd, options) {
|
|
427
|
+
logger.break();
|
|
428
|
+
const config = await readConfig(cwd);
|
|
429
|
+
const spinner = createSpinner("Fetching component registry...");
|
|
430
|
+
spinner.start();
|
|
431
|
+
const registryIndex = await fetchRegistryIndex(config);
|
|
432
|
+
spinner.succeed("Registry loaded");
|
|
433
|
+
const availableNames = registryIndex.components.map((c) => c.name);
|
|
434
|
+
const invalid = componentNames.filter(
|
|
435
|
+
(name) => !availableNames.includes(name)
|
|
436
|
+
);
|
|
437
|
+
if (invalid.length > 0) {
|
|
438
|
+
logger.error(
|
|
439
|
+
`Unknown component(s): ${invalid.map((n) => chalk2.bold(n)).join(", ")}`
|
|
440
|
+
);
|
|
441
|
+
logger.info(`Available: ${availableNames.join(", ")}`);
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
const resolveSpinner = createSpinner("Resolving dependencies...");
|
|
445
|
+
resolveSpinner.start();
|
|
446
|
+
const resolution = await resolveComponents(config, componentNames);
|
|
447
|
+
resolveSpinner.succeed("Dependencies resolved");
|
|
448
|
+
const allNames = getComponentNames(resolution);
|
|
449
|
+
logger.break();
|
|
450
|
+
console.log(chalk2.bold("Components to add:"));
|
|
451
|
+
for (const { entry, isDirectRequest } of resolution.components) {
|
|
452
|
+
const suffix = isDirectRequest ? "" : chalk2.dim(` (dependency)`);
|
|
453
|
+
console.log(` ${chalk2.green("+")} ${entry.name}${suffix}`);
|
|
454
|
+
}
|
|
455
|
+
if (resolution.utils.length > 0) {
|
|
456
|
+
logger.break();
|
|
457
|
+
console.log(chalk2.bold("Utilities to copy:"));
|
|
458
|
+
console.log(` ${resolution.utils.map((u) => `${u}.ts`).join(", ")}`);
|
|
459
|
+
}
|
|
460
|
+
const npmDeps = Object.keys(resolution.npmDependencies);
|
|
461
|
+
const optionalDeps = Object.keys(resolution.optionalNpmDependencies);
|
|
462
|
+
if (npmDeps.length > 0 || optionalDeps.length > 0) {
|
|
463
|
+
logger.break();
|
|
464
|
+
console.log(chalk2.bold("npm packages:"));
|
|
465
|
+
for (const pkg of npmDeps) {
|
|
466
|
+
console.log(` ${chalk2.green("+")} ${pkg}`);
|
|
467
|
+
}
|
|
468
|
+
for (const pkg of optionalDeps) {
|
|
469
|
+
console.log(` ${chalk2.green("+")} ${pkg} ${chalk2.dim("(optional)")}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
logger.break();
|
|
473
|
+
if (options.dryRun) {
|
|
474
|
+
logger.info("Dry run complete. No files were written.");
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
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;
|
|
486
|
+
}
|
|
487
|
+
const project = await detectProject(cwd);
|
|
488
|
+
const pm = options.packageManager ?? project.packageManager;
|
|
489
|
+
await installComponents({
|
|
490
|
+
config,
|
|
491
|
+
cwd,
|
|
492
|
+
resolution,
|
|
493
|
+
packageManager: pm,
|
|
494
|
+
force: options.force
|
|
495
|
+
});
|
|
496
|
+
logger.break();
|
|
497
|
+
logger.success(
|
|
498
|
+
`Added ${allNames.length} component(s): ${allNames.join(", ")}`
|
|
499
|
+
);
|
|
500
|
+
logger.break();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/commands/create.ts
|
|
504
|
+
import path4 from "path";
|
|
505
|
+
import chalk3 from "chalk";
|
|
506
|
+
import { execa as execa2 } from "execa";
|
|
507
|
+
import fs4 from "fs-extra";
|
|
508
|
+
import prompts2 from "prompts";
|
|
509
|
+
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
510
|
+
var ROOTNATIVE_PACKAGES = ["@rootnative/core", "@rootnative/components"];
|
|
511
|
+
async function resolveTemplateSource() {
|
|
512
|
+
const fallback = {
|
|
513
|
+
baseUrl: `${DEFAULT_CONFIG.registryUrl}/${DEFAULT_CONFIG.registryVersion}/templates`,
|
|
514
|
+
pinnedVersion: null
|
|
515
|
+
};
|
|
516
|
+
try {
|
|
517
|
+
const res = await fetch(`${NPM_REGISTRY}/@rootnative/core`);
|
|
518
|
+
if (!res.ok) return fallback;
|
|
519
|
+
const data = await res.json();
|
|
520
|
+
const version = data["dist-tags"]?.latest;
|
|
521
|
+
if (!version) return fallback;
|
|
522
|
+
const tagBaseUrl = `${DEFAULT_CONFIG.registryUrl}/v${version}/templates`;
|
|
523
|
+
const probe = await fetch(`${tagBaseUrl}/blank/package.json`);
|
|
524
|
+
if (probe.ok) {
|
|
525
|
+
return { baseUrl: tagBaseUrl, pinnedVersion: version };
|
|
526
|
+
}
|
|
527
|
+
return { baseUrl: fallback.baseUrl, pinnedVersion: version };
|
|
528
|
+
} catch {
|
|
529
|
+
return fallback;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
var TEMPLATE_CONFIGS = {
|
|
533
|
+
blank: {
|
|
534
|
+
textFiles: [
|
|
535
|
+
"package.json",
|
|
536
|
+
"app.json",
|
|
537
|
+
"tsconfig.json",
|
|
538
|
+
"babel.config.js",
|
|
539
|
+
".gitignore",
|
|
540
|
+
"index.js",
|
|
541
|
+
"App.tsx"
|
|
542
|
+
],
|
|
543
|
+
dirs: ["assets"]
|
|
544
|
+
},
|
|
545
|
+
"with-router": {
|
|
546
|
+
textFiles: [
|
|
547
|
+
"package.json",
|
|
548
|
+
"app.json",
|
|
549
|
+
"tsconfig.json",
|
|
550
|
+
"babel.config.js",
|
|
551
|
+
".gitignore",
|
|
552
|
+
"app/_layout.tsx",
|
|
553
|
+
"app/index.tsx"
|
|
554
|
+
],
|
|
555
|
+
dirs: ["assets", "app"]
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
var TEMPLATE_BINARY_FILES = [
|
|
559
|
+
"assets/icon.png",
|
|
560
|
+
"assets/splash.png",
|
|
561
|
+
"assets/adaptive-icon.png",
|
|
562
|
+
"assets/favicon.png"
|
|
563
|
+
];
|
|
564
|
+
function isValidTemplate(value) {
|
|
565
|
+
return value in TEMPLATE_CONFIGS;
|
|
566
|
+
}
|
|
567
|
+
function slugify(input) {
|
|
568
|
+
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
569
|
+
}
|
|
570
|
+
function toDisplayName(slug) {
|
|
571
|
+
return slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
572
|
+
}
|
|
573
|
+
function getInstallCommand2(pm) {
|
|
574
|
+
switch (pm) {
|
|
575
|
+
case "pnpm":
|
|
576
|
+
return "pnpm install";
|
|
577
|
+
case "yarn":
|
|
578
|
+
return "yarn";
|
|
579
|
+
case "bun":
|
|
580
|
+
return "bun install";
|
|
581
|
+
default:
|
|
582
|
+
return "npm install";
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async function fetchText(url) {
|
|
586
|
+
const res = await fetch(url);
|
|
587
|
+
if (!res.ok) {
|
|
588
|
+
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
589
|
+
}
|
|
590
|
+
return res.text();
|
|
591
|
+
}
|
|
592
|
+
async function fetchBinary(url) {
|
|
593
|
+
const res = await fetch(url);
|
|
594
|
+
if (!res.ok) return null;
|
|
595
|
+
return Buffer.from(await res.arrayBuffer());
|
|
596
|
+
}
|
|
597
|
+
async function createCommand(name, options = {}) {
|
|
598
|
+
logger.break();
|
|
599
|
+
let templateName;
|
|
600
|
+
if (options.template) {
|
|
601
|
+
if (!isValidTemplate(options.template)) {
|
|
602
|
+
logger.error(
|
|
603
|
+
`Unknown template "${options.template}". Available: blank, with-router`
|
|
604
|
+
);
|
|
605
|
+
process.exit(1);
|
|
606
|
+
}
|
|
607
|
+
templateName = options.template;
|
|
608
|
+
} else if (options.yes) {
|
|
609
|
+
templateName = "blank";
|
|
610
|
+
} else {
|
|
611
|
+
const { value } = await prompts2({
|
|
612
|
+
type: "select",
|
|
613
|
+
name: "value",
|
|
614
|
+
message: "Template:",
|
|
615
|
+
choices: [
|
|
616
|
+
{ title: "Blank", description: "Minimal setup", value: "blank" },
|
|
617
|
+
{
|
|
618
|
+
title: "With Router",
|
|
619
|
+
description: "Includes Expo Router",
|
|
620
|
+
value: "with-router"
|
|
621
|
+
}
|
|
622
|
+
],
|
|
623
|
+
initial: 0
|
|
624
|
+
});
|
|
625
|
+
if (value === void 0) {
|
|
626
|
+
logger.info("Create cancelled.");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
templateName = value;
|
|
630
|
+
}
|
|
631
|
+
let projectName;
|
|
632
|
+
if (name) {
|
|
633
|
+
projectName = slugify(name);
|
|
634
|
+
} else {
|
|
635
|
+
const { value } = await prompts2({
|
|
636
|
+
type: "text",
|
|
637
|
+
name: "value",
|
|
638
|
+
message: "Project name:",
|
|
639
|
+
initial: "my-app",
|
|
640
|
+
validate: (v) => v.trim().length > 0 || "Project name is required"
|
|
641
|
+
});
|
|
642
|
+
if (!value) {
|
|
643
|
+
logger.info("Create cancelled.");
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
projectName = slugify(value);
|
|
647
|
+
}
|
|
648
|
+
let displayName;
|
|
649
|
+
if (options.yes) {
|
|
650
|
+
displayName = toDisplayName(projectName);
|
|
651
|
+
} else {
|
|
652
|
+
const { value } = await prompts2({
|
|
653
|
+
type: "text",
|
|
654
|
+
name: "value",
|
|
655
|
+
message: "Display name (shown on home screen):",
|
|
656
|
+
initial: toDisplayName(projectName)
|
|
657
|
+
});
|
|
658
|
+
if (!value) {
|
|
659
|
+
logger.info("Create cancelled.");
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
displayName = value;
|
|
663
|
+
}
|
|
664
|
+
let packageManager;
|
|
665
|
+
if (options.packageManager) {
|
|
666
|
+
packageManager = options.packageManager;
|
|
667
|
+
} else if (options.yes) {
|
|
668
|
+
packageManager = "npm";
|
|
669
|
+
} else {
|
|
670
|
+
const { value } = await prompts2({
|
|
671
|
+
type: "select",
|
|
672
|
+
name: "value",
|
|
673
|
+
message: "Package manager:",
|
|
674
|
+
choices: [
|
|
675
|
+
{ title: "npm", value: "npm" },
|
|
676
|
+
{ title: "yarn", value: "yarn" },
|
|
677
|
+
{ title: "pnpm", value: "pnpm" },
|
|
678
|
+
{ title: "bun", value: "bun" }
|
|
679
|
+
],
|
|
680
|
+
initial: 0
|
|
681
|
+
});
|
|
682
|
+
if (value === void 0) {
|
|
683
|
+
logger.info("Create cancelled.");
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
packageManager = value;
|
|
687
|
+
}
|
|
688
|
+
const targetDir = path4.resolve(process.cwd(), projectName);
|
|
689
|
+
if (await fs4.pathExists(targetDir)) {
|
|
690
|
+
if (options.yes) {
|
|
691
|
+
logger.warn(`Directory ${chalk3.bold(projectName)} already exists.`);
|
|
692
|
+
process.exit(1);
|
|
693
|
+
}
|
|
694
|
+
const { overwrite } = await prompts2({
|
|
695
|
+
type: "confirm",
|
|
696
|
+
name: "overwrite",
|
|
697
|
+
message: `Directory ${chalk3.bold(projectName)} already exists. Overwrite?`,
|
|
698
|
+
initial: false
|
|
699
|
+
});
|
|
700
|
+
if (!overwrite) {
|
|
701
|
+
logger.info("Create cancelled.");
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
await fs4.remove(targetDir);
|
|
705
|
+
}
|
|
706
|
+
const templateConfig = TEMPLATE_CONFIGS[templateName];
|
|
707
|
+
const { baseUrl, pinnedVersion } = await resolveTemplateSource();
|
|
708
|
+
const templateBaseUrl = `${baseUrl}/${templateName}`;
|
|
709
|
+
const spinner = createSpinner("Creating project...");
|
|
710
|
+
spinner.start();
|
|
711
|
+
try {
|
|
712
|
+
for (const dir of templateConfig.dirs) {
|
|
713
|
+
await fs4.ensureDir(path4.join(targetDir, dir));
|
|
714
|
+
}
|
|
715
|
+
for (const file of templateConfig.textFiles) {
|
|
716
|
+
let content = await fetchText(`${templateBaseUrl}/${file}`);
|
|
717
|
+
if (file === "package.json") {
|
|
718
|
+
const pkg = JSON.parse(content);
|
|
719
|
+
pkg.name = projectName;
|
|
720
|
+
if (pinnedVersion) {
|
|
721
|
+
for (const pkgName of ROOTNATIVE_PACKAGES) {
|
|
722
|
+
if (pkg.dependencies?.[pkgName]) {
|
|
723
|
+
pkg.dependencies[pkgName] = pinnedVersion;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
content = JSON.stringify(pkg, null, 2) + "\n";
|
|
728
|
+
}
|
|
729
|
+
if (file === "app.json") {
|
|
730
|
+
const appJson = JSON.parse(content);
|
|
731
|
+
appJson.expo.name = displayName;
|
|
732
|
+
appJson.expo.slug = projectName;
|
|
733
|
+
if (appJson.expo.scheme) {
|
|
734
|
+
appJson.expo.scheme = projectName;
|
|
735
|
+
}
|
|
736
|
+
content = JSON.stringify(appJson, null, 2) + "\n";
|
|
737
|
+
}
|
|
738
|
+
await fs4.outputFile(path4.join(targetDir, file), content);
|
|
739
|
+
}
|
|
740
|
+
for (const file of TEMPLATE_BINARY_FILES) {
|
|
741
|
+
const buffer = await fetchBinary(`${templateBaseUrl}/${file}`);
|
|
742
|
+
if (buffer) {
|
|
743
|
+
await fs4.outputFile(path4.join(targetDir, file), buffer);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
spinner.succeed("Project created");
|
|
747
|
+
} catch (error) {
|
|
748
|
+
spinner.fail("Failed to create project");
|
|
749
|
+
throw error;
|
|
750
|
+
}
|
|
751
|
+
let shouldInstall = options.yes;
|
|
752
|
+
if (!options.yes) {
|
|
753
|
+
const { value } = await prompts2({
|
|
754
|
+
type: "confirm",
|
|
755
|
+
name: "value",
|
|
756
|
+
message: "Install dependencies?",
|
|
757
|
+
initial: true
|
|
758
|
+
});
|
|
759
|
+
shouldInstall = value;
|
|
760
|
+
}
|
|
761
|
+
if (shouldInstall) {
|
|
762
|
+
const installCmd = getInstallCommand2(packageManager);
|
|
763
|
+
const [cmd, ...args] = installCmd.split(" ");
|
|
764
|
+
logger.break();
|
|
765
|
+
logger.info("Installing dependencies...");
|
|
766
|
+
logger.break();
|
|
767
|
+
try {
|
|
768
|
+
await execa2(cmd, args, { cwd: targetDir, stdio: "inherit" });
|
|
769
|
+
logger.break();
|
|
770
|
+
logger.success("Dependencies installed");
|
|
771
|
+
} catch {
|
|
772
|
+
logger.break();
|
|
773
|
+
logger.error("Failed to install dependencies");
|
|
774
|
+
logger.info(
|
|
775
|
+
`Run manually: ${chalk3.bold(`cd ${projectName} && ${installCmd}`)}`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
logger.break();
|
|
780
|
+
logger.success(`Project ${chalk3.bold(displayName)} is ready!`);
|
|
781
|
+
logger.break();
|
|
782
|
+
logger.info("Next steps:");
|
|
783
|
+
logger.info(` cd ${projectName}`);
|
|
784
|
+
if (!shouldInstall) {
|
|
785
|
+
logger.info(` ${getInstallCommand2(packageManager)}`);
|
|
786
|
+
}
|
|
787
|
+
logger.info(" npx expo start");
|
|
788
|
+
logger.break();
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/commands/doctor.ts
|
|
792
|
+
import path5 from "path";
|
|
793
|
+
import chalk4 from "chalk";
|
|
794
|
+
import fs5 from "fs-extra";
|
|
795
|
+
function logCheck(status, message) {
|
|
796
|
+
const icon = status === "pass" ? chalk4.green("[pass]") : status === "warn" ? chalk4.yellow("[warn]") : chalk4.red("[fail]");
|
|
797
|
+
console.log(` ${icon} ${message}`);
|
|
798
|
+
}
|
|
799
|
+
async function doctorCommand(cwd) {
|
|
800
|
+
logger.break();
|
|
801
|
+
console.log(chalk4.bold("RootNative Doctor"));
|
|
802
|
+
logger.break();
|
|
803
|
+
let issues = 0;
|
|
804
|
+
if (await configExists(cwd)) {
|
|
805
|
+
logCheck("pass", "rootnative.json found");
|
|
806
|
+
} 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;
|
|
812
|
+
}
|
|
813
|
+
const config = await readConfig(cwd);
|
|
814
|
+
const project = await detectProject(cwd);
|
|
815
|
+
if (project.type !== "unknown") {
|
|
816
|
+
logCheck(
|
|
817
|
+
"pass",
|
|
818
|
+
`${project.type} project detected (${project.packageManager})`
|
|
819
|
+
);
|
|
820
|
+
} else {
|
|
821
|
+
logCheck("fail", "Not a React Native or Expo project");
|
|
822
|
+
issues++;
|
|
823
|
+
}
|
|
824
|
+
const pkgPath = path5.resolve(cwd, "package.json");
|
|
825
|
+
if (await fs5.pathExists(pkgPath)) {
|
|
826
|
+
const pkg = await fs5.readJSON(pkgPath);
|
|
827
|
+
const allDeps = {
|
|
828
|
+
...pkg.dependencies,
|
|
829
|
+
...pkg.devDependencies
|
|
830
|
+
};
|
|
831
|
+
const rnVersion = allDeps["react-native"];
|
|
832
|
+
if (rnVersion) {
|
|
833
|
+
logCheck("pass", `react-native: ${rnVersion}`);
|
|
834
|
+
} else {
|
|
835
|
+
logCheck("fail", "react-native not found in dependencies");
|
|
836
|
+
issues++;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const corePkgPath = path5.resolve(
|
|
840
|
+
cwd,
|
|
841
|
+
"node_modules",
|
|
842
|
+
"@rootnative",
|
|
843
|
+
"core",
|
|
844
|
+
"package.json"
|
|
845
|
+
);
|
|
846
|
+
if (await fs5.pathExists(corePkgPath)) {
|
|
847
|
+
const corePkg = await fs5.readJSON(corePkgPath);
|
|
848
|
+
logCheck("pass", `@rootnative/core@${corePkg.version} installed`);
|
|
849
|
+
} else {
|
|
850
|
+
logCheck(
|
|
851
|
+
"fail",
|
|
852
|
+
'@rootnative/core not installed. Run "rootnative init" to install it.'
|
|
853
|
+
);
|
|
854
|
+
issues++;
|
|
855
|
+
}
|
|
856
|
+
if (project.hasTypeScript) {
|
|
857
|
+
logCheck("pass", "TypeScript configured");
|
|
858
|
+
} else {
|
|
859
|
+
logCheck(
|
|
860
|
+
"warn",
|
|
861
|
+
"TypeScript not detected. RootNative components use TypeScript."
|
|
862
|
+
);
|
|
863
|
+
}
|
|
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;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (integrityOk) {
|
|
885
|
+
logCheck(
|
|
886
|
+
"pass",
|
|
887
|
+
`${componentDirs.length} component(s) installed, all files present`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
logCheck(
|
|
892
|
+
"warn",
|
|
893
|
+
'No components installed yet. Run "rootnative add <component>".'
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
} else {
|
|
897
|
+
logCheck(
|
|
898
|
+
"warn",
|
|
899
|
+
`Components directory not found at ${config.aliases.components}`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
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
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
const nodeModules = path5.resolve(cwd, "node_modules");
|
|
915
|
+
const safeAreaInstalled = await fs5.pathExists(
|
|
916
|
+
path5.join(nodeModules, "react-native-safe-area-context")
|
|
917
|
+
);
|
|
918
|
+
if (safeAreaInstalled) {
|
|
919
|
+
logCheck("pass", "react-native-safe-area-context installed");
|
|
920
|
+
} else {
|
|
921
|
+
logCheck(
|
|
922
|
+
"warn",
|
|
923
|
+
"react-native-safe-area-context not installed (needed by: appbar, layout)"
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
const vectorIconsInstalled = await fs5.pathExists(
|
|
927
|
+
path5.join(nodeModules, "@expo", "vector-icons")
|
|
928
|
+
);
|
|
929
|
+
if (vectorIconsInstalled) {
|
|
930
|
+
logCheck("pass", "@expo/vector-icons installed");
|
|
931
|
+
} else {
|
|
932
|
+
logCheck(
|
|
933
|
+
"warn",
|
|
934
|
+
"@expo/vector-icons not installed (needed for icon support)"
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
logger.break();
|
|
938
|
+
if (issues > 0) {
|
|
939
|
+
logger.error(`${issues} issue(s) found.`);
|
|
940
|
+
} else {
|
|
941
|
+
logger.success("All checks passed!");
|
|
942
|
+
}
|
|
943
|
+
logger.break();
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/commands/init.ts
|
|
947
|
+
import chalk5 from "chalk";
|
|
948
|
+
import { execa as execa3 } from "execa";
|
|
949
|
+
import prompts3 from "prompts";
|
|
950
|
+
async function initCommand(cwd, options = {}) {
|
|
951
|
+
logger.break();
|
|
952
|
+
if (await configExists(cwd)) {
|
|
953
|
+
if (options.yes) {
|
|
954
|
+
logger.info("Overwriting existing rootnative.json");
|
|
955
|
+
} else {
|
|
956
|
+
const { overwrite } = await prompts3({
|
|
957
|
+
type: "confirm",
|
|
958
|
+
name: "overwrite",
|
|
959
|
+
message: "rootnative.json already exists. Overwrite?",
|
|
960
|
+
initial: false
|
|
961
|
+
});
|
|
962
|
+
if (!overwrite) {
|
|
963
|
+
logger.info("Init cancelled.");
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
const spinner = createSpinner("Detecting project...");
|
|
969
|
+
spinner.start();
|
|
970
|
+
const project = await detectProject(cwd);
|
|
971
|
+
spinner.stop();
|
|
972
|
+
if (project.type === "unknown") {
|
|
973
|
+
logger.error("No React Native or Expo project detected.");
|
|
974
|
+
logger.info(
|
|
975
|
+
"Make sure you are in a project with react-native or expo in package.json."
|
|
976
|
+
);
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
logger.info(
|
|
980
|
+
`Detected ${chalk5.bold(project.type)} project using ${chalk5.bold(project.packageManager)}`
|
|
981
|
+
);
|
|
982
|
+
if (!project.hasTypeScript) {
|
|
983
|
+
logger.warn(
|
|
984
|
+
"TypeScript not detected. RootNative components use TypeScript."
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
let defaultComponentsAlias = DEFAULT_CONFIG.aliases.components;
|
|
988
|
+
let defaultLibAlias = DEFAULT_CONFIG.aliases.lib;
|
|
989
|
+
if (project.aliases?.["@"]) {
|
|
990
|
+
defaultComponentsAlias = "@/components/ui";
|
|
991
|
+
defaultLibAlias = "@/lib";
|
|
992
|
+
} else if (project.aliases?.["~"]) {
|
|
993
|
+
defaultComponentsAlias = "~/components/ui";
|
|
994
|
+
defaultLibAlias = "~/lib";
|
|
995
|
+
}
|
|
996
|
+
let componentsAlias;
|
|
997
|
+
let libAlias;
|
|
998
|
+
if (options.yes) {
|
|
999
|
+
componentsAlias = options.componentsAlias ?? defaultComponentsAlias;
|
|
1000
|
+
libAlias = options.libAlias ?? defaultLibAlias;
|
|
1001
|
+
} else {
|
|
1002
|
+
const answers = await prompts3([
|
|
1003
|
+
{
|
|
1004
|
+
type: "text",
|
|
1005
|
+
name: "componentsAlias",
|
|
1006
|
+
message: "Where should components be installed?",
|
|
1007
|
+
initial: options.componentsAlias ?? defaultComponentsAlias
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
type: "text",
|
|
1011
|
+
name: "libAlias",
|
|
1012
|
+
message: "Where should utility files be placed?",
|
|
1013
|
+
initial: options.libAlias ?? defaultLibAlias
|
|
1014
|
+
}
|
|
1015
|
+
]);
|
|
1016
|
+
if (!answers.componentsAlias || !answers.libAlias) {
|
|
1017
|
+
logger.info("Init cancelled.");
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
componentsAlias = answers.componentsAlias;
|
|
1021
|
+
libAlias = answers.libAlias;
|
|
1022
|
+
}
|
|
1023
|
+
const config = {
|
|
1024
|
+
...DEFAULT_CONFIG,
|
|
1025
|
+
aliases: {
|
|
1026
|
+
components: componentsAlias,
|
|
1027
|
+
lib: libAlias
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
await writeConfig(cwd, config);
|
|
1031
|
+
logger.success("Created rootnative.json");
|
|
1032
|
+
let installCore = options.yes;
|
|
1033
|
+
if (!options.yes) {
|
|
1034
|
+
const answer = await prompts3({
|
|
1035
|
+
type: "confirm",
|
|
1036
|
+
name: "installCore",
|
|
1037
|
+
message: "Install @rootnative/core?",
|
|
1038
|
+
initial: true
|
|
1039
|
+
});
|
|
1040
|
+
installCore = answer.installCore;
|
|
1041
|
+
}
|
|
1042
|
+
if (installCore) {
|
|
1043
|
+
const pm = options.packageManager ?? project.packageManager;
|
|
1044
|
+
const command = getInstallCommand(pm, ["@rootnative/core"]);
|
|
1045
|
+
const [cmd, ...args] = command.split(" ");
|
|
1046
|
+
logger.break();
|
|
1047
|
+
logger.info("Installing @rootnative/core...");
|
|
1048
|
+
logger.break();
|
|
1049
|
+
try {
|
|
1050
|
+
await execa3(cmd, args, { cwd, stdio: "inherit" });
|
|
1051
|
+
logger.break();
|
|
1052
|
+
logger.success("Installed @rootnative/core");
|
|
1053
|
+
} catch {
|
|
1054
|
+
logger.break();
|
|
1055
|
+
logger.error("Failed to install @rootnative/core");
|
|
1056
|
+
logger.info(`Run manually: ${chalk5.bold(command)}`);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
logger.break();
|
|
1060
|
+
logger.success("Project initialized!");
|
|
1061
|
+
logger.info(
|
|
1062
|
+
`Add components with: ${chalk5.bold("npx rootnative add <component>")}`
|
|
1063
|
+
);
|
|
1064
|
+
logger.break();
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// src/commands/list.ts
|
|
1068
|
+
import path6 from "path";
|
|
1069
|
+
import chalk6 from "chalk";
|
|
1070
|
+
import fs6 from "fs-extra";
|
|
1071
|
+
async function listCommand(cwd) {
|
|
1072
|
+
logger.break();
|
|
1073
|
+
const config = await readConfig(cwd);
|
|
1074
|
+
const componentsDir = resolveAliasPath(
|
|
1075
|
+
config.aliases.components,
|
|
1076
|
+
cwd
|
|
1077
|
+
);
|
|
1078
|
+
const spinner = createSpinner(
|
|
1079
|
+
"Fetching component registry..."
|
|
1080
|
+
);
|
|
1081
|
+
spinner.start();
|
|
1082
|
+
const registryIndex = await fetchRegistryIndex(config);
|
|
1083
|
+
spinner.succeed("Registry loaded");
|
|
1084
|
+
logger.break();
|
|
1085
|
+
console.log(
|
|
1086
|
+
chalk6.bold(`Available components (v${registryIndex.version}):`)
|
|
1087
|
+
);
|
|
1088
|
+
logger.break();
|
|
1089
|
+
console.log(
|
|
1090
|
+
` ${chalk6.dim(padEnd("Name", 28))}${chalk6.dim(padEnd("Status", 14))}${chalk6.dim("Description")}`
|
|
1091
|
+
);
|
|
1092
|
+
console.log(
|
|
1093
|
+
` ${chalk6.dim("-".repeat(70))}`
|
|
1094
|
+
);
|
|
1095
|
+
for (const component of registryIndex.components) {
|
|
1096
|
+
const componentDir = path6.join(componentsDir, component.name);
|
|
1097
|
+
const installed = await fs6.pathExists(componentDir);
|
|
1098
|
+
const status = installed ? chalk6.green("installed") : chalk6.dim("-");
|
|
1099
|
+
const nameDisplay = installed ? chalk6.green(component.name) : component.name;
|
|
1100
|
+
console.log(
|
|
1101
|
+
` ${padEnd(nameDisplay, installed ? 28 + 10 : 28)}${padEnd(status, installed ? 14 + 10 : 14)}${chalk6.dim(component.description)}`
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
logger.break();
|
|
1105
|
+
}
|
|
1106
|
+
function padEnd(str, length) {
|
|
1107
|
+
const visibleLength = str.replace(
|
|
1108
|
+
// eslint-disable-next-line no-control-regex
|
|
1109
|
+
/\u001b\[\d+m/g,
|
|
1110
|
+
""
|
|
1111
|
+
).length;
|
|
1112
|
+
const padding = Math.max(0, length - visibleLength);
|
|
1113
|
+
return str + " ".repeat(padding);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// src/commands/update.ts
|
|
1117
|
+
import path7 from "path";
|
|
1118
|
+
import chalk8 from "chalk";
|
|
1119
|
+
import fs7 from "fs-extra";
|
|
1120
|
+
import prompts4 from "prompts";
|
|
1121
|
+
|
|
1122
|
+
// src/lib/diff.ts
|
|
1123
|
+
import chalk7 from "chalk";
|
|
1124
|
+
function computeDiff(oldContent, newContent, fileName) {
|
|
1125
|
+
const oldLines = oldContent.split("\n");
|
|
1126
|
+
const newLines = newContent.split("\n");
|
|
1127
|
+
const lines = [];
|
|
1128
|
+
let additions = 0;
|
|
1129
|
+
let deletions = 0;
|
|
1130
|
+
const lcs = buildLCS(oldLines, newLines);
|
|
1131
|
+
let oldIdx = 0;
|
|
1132
|
+
let newIdx = 0;
|
|
1133
|
+
for (const [lcsOld, lcsNew] of lcs) {
|
|
1134
|
+
while (oldIdx < lcsOld) {
|
|
1135
|
+
lines.push({ type: "remove", content: oldLines[oldIdx] });
|
|
1136
|
+
deletions++;
|
|
1137
|
+
oldIdx++;
|
|
1138
|
+
}
|
|
1139
|
+
while (newIdx < lcsNew) {
|
|
1140
|
+
lines.push({ type: "add", content: newLines[newIdx] });
|
|
1141
|
+
additions++;
|
|
1142
|
+
newIdx++;
|
|
1143
|
+
}
|
|
1144
|
+
lines.push({ type: "context", content: oldLines[oldIdx] });
|
|
1145
|
+
oldIdx++;
|
|
1146
|
+
newIdx++;
|
|
1147
|
+
}
|
|
1148
|
+
while (oldIdx < oldLines.length) {
|
|
1149
|
+
lines.push({ type: "remove", content: oldLines[oldIdx] });
|
|
1150
|
+
deletions++;
|
|
1151
|
+
oldIdx++;
|
|
1152
|
+
}
|
|
1153
|
+
while (newIdx < newLines.length) {
|
|
1154
|
+
lines.push({ type: "add", content: newLines[newIdx] });
|
|
1155
|
+
additions++;
|
|
1156
|
+
newIdx++;
|
|
1157
|
+
}
|
|
1158
|
+
return {
|
|
1159
|
+
fileName,
|
|
1160
|
+
hasChanges: additions > 0 || deletions > 0,
|
|
1161
|
+
additions,
|
|
1162
|
+
deletions,
|
|
1163
|
+
lines
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
function buildLCS(a, b) {
|
|
1167
|
+
const m = a.length;
|
|
1168
|
+
const n = b.length;
|
|
1169
|
+
const dp = Array.from(
|
|
1170
|
+
{ length: m + 1 },
|
|
1171
|
+
() => Array(n + 1).fill(0)
|
|
1172
|
+
);
|
|
1173
|
+
for (let i2 = 1; i2 <= m; i2++) {
|
|
1174
|
+
for (let j2 = 1; j2 <= n; j2++) {
|
|
1175
|
+
if (a[i2 - 1] === b[j2 - 1]) {
|
|
1176
|
+
dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
1177
|
+
} else {
|
|
1178
|
+
dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
const result = [];
|
|
1183
|
+
let i = m;
|
|
1184
|
+
let j = n;
|
|
1185
|
+
while (i > 0 && j > 0) {
|
|
1186
|
+
if (a[i - 1] === b[j - 1]) {
|
|
1187
|
+
result.unshift([i - 1, j - 1]);
|
|
1188
|
+
i--;
|
|
1189
|
+
j--;
|
|
1190
|
+
} else if (dp[i - 1][j] > dp[i][j - 1]) {
|
|
1191
|
+
i--;
|
|
1192
|
+
} else {
|
|
1193
|
+
j--;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return result;
|
|
1197
|
+
}
|
|
1198
|
+
function formatDiff(diff, context = 3) {
|
|
1199
|
+
if (!diff.hasChanges) {
|
|
1200
|
+
return chalk7.dim(` ${diff.fileName}: no changes`);
|
|
1201
|
+
}
|
|
1202
|
+
const output = [];
|
|
1203
|
+
output.push(
|
|
1204
|
+
chalk7.bold(` ${diff.fileName}`) + chalk7.green(` +${diff.additions}`) + chalk7.red(` -${diff.deletions}`)
|
|
1205
|
+
);
|
|
1206
|
+
const changeIndices = /* @__PURE__ */ new Set();
|
|
1207
|
+
diff.lines.forEach((line, idx) => {
|
|
1208
|
+
if (line.type !== "context") {
|
|
1209
|
+
for (let c = Math.max(0, idx - context); c <= Math.min(diff.lines.length - 1, idx + context); c++) {
|
|
1210
|
+
changeIndices.add(c);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
let lastPrinted = -1;
|
|
1215
|
+
for (const idx of Array.from(changeIndices).sort(
|
|
1216
|
+
(a, b) => a - b
|
|
1217
|
+
)) {
|
|
1218
|
+
if (lastPrinted !== -1 && idx > lastPrinted + 1) {
|
|
1219
|
+
output.push(chalk7.dim(" ..."));
|
|
1220
|
+
}
|
|
1221
|
+
const line = diff.lines[idx];
|
|
1222
|
+
if (line.type === "add") {
|
|
1223
|
+
output.push(chalk7.green(` + ${line.content}`));
|
|
1224
|
+
} else if (line.type === "remove") {
|
|
1225
|
+
output.push(chalk7.red(` - ${line.content}`));
|
|
1226
|
+
} else {
|
|
1227
|
+
output.push(chalk7.dim(` ${line.content}`));
|
|
1228
|
+
}
|
|
1229
|
+
lastPrinted = idx;
|
|
1230
|
+
}
|
|
1231
|
+
return output.join("\n");
|
|
1232
|
+
}
|
|
1233
|
+
function formatDiffSummary(diffs) {
|
|
1234
|
+
const changed = diffs.filter((d) => d.hasChanges);
|
|
1235
|
+
if (changed.length === 0) {
|
|
1236
|
+
return chalk7.dim(" No changes detected");
|
|
1237
|
+
}
|
|
1238
|
+
const totalAdd = changed.reduce(
|
|
1239
|
+
(sum, d) => sum + d.additions,
|
|
1240
|
+
0
|
|
1241
|
+
);
|
|
1242
|
+
const totalDel = changed.reduce(
|
|
1243
|
+
(sum, d) => sum + d.deletions,
|
|
1244
|
+
0
|
|
1245
|
+
);
|
|
1246
|
+
return ` ${changed.length} file(s) changed: ` + chalk7.green(`+${totalAdd}`) + " " + chalk7.red(`-${totalDel}`);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// src/commands/update.ts
|
|
1250
|
+
async function updateCommand(componentNames, cwd, options) {
|
|
1251
|
+
logger.break();
|
|
1252
|
+
const config = await readConfig(cwd);
|
|
1253
|
+
const componentsDir = resolveAliasPath(config.aliases.components, cwd);
|
|
1254
|
+
const libDir = resolveAliasPath(config.aliases.lib, cwd);
|
|
1255
|
+
const spinner = createSpinner("Checking installed components...");
|
|
1256
|
+
spinner.start();
|
|
1257
|
+
let targetNames;
|
|
1258
|
+
if (options.all) {
|
|
1259
|
+
const installed = await getInstalledComponents(componentsDir);
|
|
1260
|
+
if (installed.length === 0) {
|
|
1261
|
+
spinner.fail("No components installed");
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
targetNames = installed;
|
|
1265
|
+
} else if (componentNames.length > 0) {
|
|
1266
|
+
targetNames = componentNames;
|
|
1267
|
+
const missing = [];
|
|
1268
|
+
for (const name of targetNames) {
|
|
1269
|
+
const dir = path7.join(componentsDir, name);
|
|
1270
|
+
if (!await fs7.pathExists(dir)) {
|
|
1271
|
+
missing.push(name);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
if (missing.length > 0) {
|
|
1275
|
+
spinner.fail(
|
|
1276
|
+
`Not installed: ${missing.join(", ")}. Use "rootnative add" instead.`
|
|
1277
|
+
);
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
} else {
|
|
1281
|
+
spinner.fail("Specify component names or use --all to update everything.");
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
spinner.succeed(`Found ${targetNames.length} component(s) to check`);
|
|
1285
|
+
const resolveSpinner = createSpinner("Fetching latest from registry...");
|
|
1286
|
+
resolveSpinner.start();
|
|
1287
|
+
const resolution = await resolveComponents(config, targetNames);
|
|
1288
|
+
const allComponentNames = getComponentNames(resolution);
|
|
1289
|
+
const utilsRegistry = await fetchUtilsRegistry(config);
|
|
1290
|
+
resolveSpinner.succeed("Registry fetched");
|
|
1291
|
+
const componentDiffs = [];
|
|
1292
|
+
for (const { entry } of resolution.components) {
|
|
1293
|
+
const componentDir = path7.join(componentsDir, entry.name);
|
|
1294
|
+
if (!await fs7.pathExists(componentDir)) {
|
|
1295
|
+
componentDiffs.push({
|
|
1296
|
+
name: entry.name,
|
|
1297
|
+
diffs: [],
|
|
1298
|
+
hasChanges: true
|
|
1299
|
+
});
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
const diffs = [];
|
|
1303
|
+
for (const filePath of entry.files) {
|
|
1304
|
+
const fileName = path7.basename(filePath);
|
|
1305
|
+
const localPath = path7.join(componentDir, fileName);
|
|
1306
|
+
const remoteContent = await fetchFileContent(config, filePath);
|
|
1307
|
+
const transformed = transformImports(remoteContent, {
|
|
1308
|
+
config,
|
|
1309
|
+
componentName: entry.name,
|
|
1310
|
+
installedComponents: allComponentNames
|
|
1311
|
+
});
|
|
1312
|
+
let localContent = "";
|
|
1313
|
+
if (await fs7.pathExists(localPath)) {
|
|
1314
|
+
localContent = await fs7.readFile(localPath, "utf-8");
|
|
1315
|
+
}
|
|
1316
|
+
diffs.push(computeDiff(localContent, transformed, fileName));
|
|
1317
|
+
}
|
|
1318
|
+
const hasChanges = diffs.some((d) => d.hasChanges);
|
|
1319
|
+
componentDiffs.push({ name: entry.name, diffs, hasChanges });
|
|
1320
|
+
}
|
|
1321
|
+
const utilDiffs = [];
|
|
1322
|
+
for (const utilName of resolution.utils) {
|
|
1323
|
+
const utilEntry = utilsRegistry[utilName];
|
|
1324
|
+
if (!utilEntry) continue;
|
|
1325
|
+
const fileName = path7.basename(utilEntry.file);
|
|
1326
|
+
const localPath = path7.join(libDir, fileName);
|
|
1327
|
+
const remoteContent = await fetchFileContent(config, utilEntry.file);
|
|
1328
|
+
let localContent = "";
|
|
1329
|
+
if (await fs7.pathExists(localPath)) {
|
|
1330
|
+
localContent = await fs7.readFile(localPath, "utf-8");
|
|
1331
|
+
}
|
|
1332
|
+
utilDiffs.push(computeDiff(localContent, remoteContent, `lib/${fileName}`));
|
|
1333
|
+
}
|
|
1334
|
+
const utilExports = {};
|
|
1335
|
+
for (const utilName of resolution.utils) {
|
|
1336
|
+
const utilEntry = utilsRegistry[utilName];
|
|
1337
|
+
if (utilEntry) {
|
|
1338
|
+
utilExports[utilName] = utilEntry.exports;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
const newBarrel = generateUtilsBarrel(resolution.utils, utilExports);
|
|
1342
|
+
const barrelPath = path7.join(libDir, "rootnative-utils.ts");
|
|
1343
|
+
let oldBarrel = "";
|
|
1344
|
+
if (await fs7.pathExists(barrelPath)) {
|
|
1345
|
+
oldBarrel = await fs7.readFile(barrelPath, "utf-8");
|
|
1346
|
+
}
|
|
1347
|
+
utilDiffs.push(computeDiff(oldBarrel, newBarrel, "lib/rootnative-utils.ts"));
|
|
1348
|
+
const changedComponents = componentDiffs.filter((c) => c.hasChanges);
|
|
1349
|
+
const changedUtils = utilDiffs.filter((d) => d.hasChanges);
|
|
1350
|
+
const totalChanges = changedComponents.length + (changedUtils.length > 0 ? 1 : 0);
|
|
1351
|
+
if (totalChanges === 0) {
|
|
1352
|
+
logger.break();
|
|
1353
|
+
logger.success("All components are up to date!");
|
|
1354
|
+
logger.break();
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
logger.break();
|
|
1358
|
+
console.log(
|
|
1359
|
+
chalk8.bold(`${changedComponents.length} component(s) with updates:`)
|
|
1360
|
+
);
|
|
1361
|
+
logger.break();
|
|
1362
|
+
for (const comp of componentDiffs) {
|
|
1363
|
+
if (!comp.hasChanges) {
|
|
1364
|
+
console.log(chalk8.dim(` ${comp.name}: up to date`));
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
if (comp.diffs.length === 0) {
|
|
1368
|
+
console.log(
|
|
1369
|
+
` ${chalk8.yellow(comp.name)}: ${chalk8.yellow("new dependency (will be added)")}`
|
|
1370
|
+
);
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
console.log(chalk8.bold.yellow(` ${comp.name}:`));
|
|
1374
|
+
console.log(formatDiffSummary(comp.diffs));
|
|
1375
|
+
for (const diff of comp.diffs) {
|
|
1376
|
+
if (diff.hasChanges) {
|
|
1377
|
+
console.log(formatDiff(diff));
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
logger.break();
|
|
1381
|
+
}
|
|
1382
|
+
if (changedUtils.length > 0) {
|
|
1383
|
+
console.log(chalk8.bold("Utility updates:"));
|
|
1384
|
+
for (const diff of changedUtils) {
|
|
1385
|
+
if (diff.hasChanges) {
|
|
1386
|
+
console.log(formatDiff(diff));
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
logger.break();
|
|
1390
|
+
}
|
|
1391
|
+
if (options.dryRun) {
|
|
1392
|
+
logger.info("Dry run complete. No files were written.");
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const { proceed } = await prompts4({
|
|
1396
|
+
type: "confirm",
|
|
1397
|
+
name: "proceed",
|
|
1398
|
+
message: `Apply updates to ${changedComponents.length} component(s)?`,
|
|
1399
|
+
initial: true
|
|
1400
|
+
});
|
|
1401
|
+
if (!proceed) {
|
|
1402
|
+
logger.info("Cancelled.");
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
const applySpinner = createSpinner("Applying updates...");
|
|
1406
|
+
applySpinner.start();
|
|
1407
|
+
for (const comp of changedComponents) {
|
|
1408
|
+
const componentDir = path7.join(componentsDir, comp.name);
|
|
1409
|
+
await fs7.ensureDir(componentDir);
|
|
1410
|
+
if (comp.diffs.length === 0) {
|
|
1411
|
+
const entry = resolution.components.find(
|
|
1412
|
+
(c) => c.entry.name === comp.name
|
|
1413
|
+
);
|
|
1414
|
+
if (!entry) continue;
|
|
1415
|
+
for (const filePath of entry.entry.files) {
|
|
1416
|
+
const content = await fetchFileContent(config, filePath);
|
|
1417
|
+
const fileName = path7.basename(filePath);
|
|
1418
|
+
const transformed = transformImports(content, {
|
|
1419
|
+
config,
|
|
1420
|
+
componentName: comp.name,
|
|
1421
|
+
installedComponents: allComponentNames
|
|
1422
|
+
});
|
|
1423
|
+
await fs7.writeFile(
|
|
1424
|
+
path7.join(componentDir, fileName),
|
|
1425
|
+
transformed,
|
|
1426
|
+
"utf-8"
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
for (const diff of comp.diffs) {
|
|
1432
|
+
if (!diff.hasChanges) continue;
|
|
1433
|
+
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),
|
|
1436
|
+
newContent,
|
|
1437
|
+
"utf-8"
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
await fs7.ensureDir(libDir);
|
|
1442
|
+
for (const diff of changedUtils) {
|
|
1443
|
+
if (!diff.hasChanges) continue;
|
|
1444
|
+
const newContent = diff.lines.filter((l) => l.type !== "remove").map((l) => l.content).join("\n");
|
|
1445
|
+
const filePath = path7.join(
|
|
1446
|
+
libDir,
|
|
1447
|
+
// diff.fileName is like "lib/color.ts" — strip the "lib/" prefix
|
|
1448
|
+
diff.fileName.replace(/^lib\//, "")
|
|
1449
|
+
);
|
|
1450
|
+
await fs7.writeFile(filePath, newContent, "utf-8");
|
|
1451
|
+
}
|
|
1452
|
+
applySpinner.succeed("Updates applied");
|
|
1453
|
+
logger.break();
|
|
1454
|
+
logger.success(
|
|
1455
|
+
`Updated ${changedComponents.length} component(s): ${changedComponents.map((c) => c.name).join(", ")}`
|
|
1456
|
+
);
|
|
1457
|
+
logger.break();
|
|
1458
|
+
}
|
|
1459
|
+
async function getInstalledComponents(componentsDir) {
|
|
1460
|
+
if (!await fs7.pathExists(componentsDir)) {
|
|
1461
|
+
return [];
|
|
1462
|
+
}
|
|
1463
|
+
const entries = await fs7.readdir(componentsDir);
|
|
1464
|
+
const components = [];
|
|
1465
|
+
for (const entry of entries) {
|
|
1466
|
+
const fullPath = path7.join(componentsDir, entry);
|
|
1467
|
+
const stat = await fs7.stat(fullPath);
|
|
1468
|
+
if (stat.isDirectory()) {
|
|
1469
|
+
const indexPath = path7.join(fullPath, "index.ts");
|
|
1470
|
+
if (await fs7.pathExists(indexPath)) {
|
|
1471
|
+
components.push(entry);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
return components;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// src/commands/upgrade.ts
|
|
1479
|
+
import path8 from "path";
|
|
1480
|
+
import chalk9 from "chalk";
|
|
1481
|
+
import { execa as execa4 } from "execa";
|
|
1482
|
+
import fs8 from "fs-extra";
|
|
1483
|
+
import prompts5 from "prompts";
|
|
1484
|
+
async function fetchLatestFromNpm(packageName) {
|
|
1485
|
+
const url = `https://registry.npmjs.org/${packageName}/latest`;
|
|
1486
|
+
const response = await fetch(url);
|
|
1487
|
+
if (!response.ok) {
|
|
1488
|
+
throw new Error(
|
|
1489
|
+
`Failed to fetch ${packageName} from npm: ${response.status} ${response.statusText}`
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
return response.json();
|
|
1493
|
+
}
|
|
1494
|
+
function getInstalledPackageInfo(cwd, packageName) {
|
|
1495
|
+
const pkgPath = path8.resolve(
|
|
1496
|
+
cwd,
|
|
1497
|
+
"node_modules",
|
|
1498
|
+
...packageName.split("/"),
|
|
1499
|
+
"package.json"
|
|
1500
|
+
);
|
|
1501
|
+
if (!fs8.pathExistsSync(pkgPath)) {
|
|
1502
|
+
return null;
|
|
1503
|
+
}
|
|
1504
|
+
const pkg = fs8.readJSONSync(pkgPath);
|
|
1505
|
+
return {
|
|
1506
|
+
version: pkg.version,
|
|
1507
|
+
peerDependencies: pkg.peerDependencies,
|
|
1508
|
+
peerDependenciesMeta: pkg.peerDependenciesMeta
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
function diffPeerDeps(current, latest) {
|
|
1512
|
+
const currentDeps = current ?? {};
|
|
1513
|
+
const latestDeps = latest ?? {};
|
|
1514
|
+
const added = {};
|
|
1515
|
+
const changed = {};
|
|
1516
|
+
const removed = [];
|
|
1517
|
+
for (const [pkg, range] of Object.entries(latestDeps)) {
|
|
1518
|
+
if (!(pkg in currentDeps)) {
|
|
1519
|
+
added[pkg] = range;
|
|
1520
|
+
} else if (currentDeps[pkg] !== range) {
|
|
1521
|
+
changed[pkg] = {
|
|
1522
|
+
from: currentDeps[pkg],
|
|
1523
|
+
to: range
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
for (const pkg of Object.keys(currentDeps)) {
|
|
1528
|
+
if (!(pkg in latestDeps)) {
|
|
1529
|
+
removed.push(pkg);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
return { added, changed, removed };
|
|
1533
|
+
}
|
|
1534
|
+
function getProjectDeps(cwd) {
|
|
1535
|
+
const pkgPath = path8.resolve(cwd, "package.json");
|
|
1536
|
+
try {
|
|
1537
|
+
const pkg = fs8.readJSONSync(pkgPath);
|
|
1538
|
+
return {
|
|
1539
|
+
...pkg.dependencies,
|
|
1540
|
+
...pkg.devDependencies
|
|
1541
|
+
};
|
|
1542
|
+
} catch {
|
|
1543
|
+
return {};
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
function hasDiffChanges(diff) {
|
|
1547
|
+
return Object.keys(diff.added).length > 0 || Object.keys(diff.changed).length > 0 || diff.removed.length > 0;
|
|
1548
|
+
}
|
|
1549
|
+
async function upgradeCommand(cwd, options = {}) {
|
|
1550
|
+
logger.break();
|
|
1551
|
+
await readConfig(cwd);
|
|
1552
|
+
const spinner = createSpinner("Detecting project...");
|
|
1553
|
+
spinner.start();
|
|
1554
|
+
const project = await detectProject(cwd);
|
|
1555
|
+
spinner.stop();
|
|
1556
|
+
if (project.type === "unknown") {
|
|
1557
|
+
logger.error("No React Native or Expo project detected.");
|
|
1558
|
+
process.exit(1);
|
|
1559
|
+
}
|
|
1560
|
+
const installed = getInstalledPackageInfo(cwd, "@rootnative/core");
|
|
1561
|
+
if (!installed) {
|
|
1562
|
+
logger.error(
|
|
1563
|
+
'@rootnative/core is not installed. Run "rootnative init" first.'
|
|
1564
|
+
);
|
|
1565
|
+
process.exit(1);
|
|
1566
|
+
}
|
|
1567
|
+
const fetchSpinner = createSpinner("Checking for updates...");
|
|
1568
|
+
fetchSpinner.start();
|
|
1569
|
+
const latest = await fetchLatestFromNpm("@rootnative/core");
|
|
1570
|
+
fetchSpinner.succeed("Checked npm registry");
|
|
1571
|
+
if (installed.version === latest.version) {
|
|
1572
|
+
logger.break();
|
|
1573
|
+
logger.success(
|
|
1574
|
+
`Already on the latest version ${chalk9.bold(`v${installed.version}`)}`
|
|
1575
|
+
);
|
|
1576
|
+
logger.break();
|
|
1577
|
+
if (options.all) {
|
|
1578
|
+
logger.info("Updating installed components...");
|
|
1579
|
+
await updateCommand([], cwd, { all: true, dryRun: false });
|
|
1580
|
+
}
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
logger.break();
|
|
1584
|
+
logger.info(`Installed: ${chalk9.bold(`v${installed.version}`)}`);
|
|
1585
|
+
logger.info(`Latest: ${chalk9.bold(chalk9.green(`v${latest.version}`))}`);
|
|
1586
|
+
const diff = diffPeerDeps(installed.peerDependencies, latest.peerDependencies);
|
|
1587
|
+
if (hasDiffChanges(diff)) {
|
|
1588
|
+
logger.break();
|
|
1589
|
+
console.log(chalk9.bold("Peer dependency changes:"));
|
|
1590
|
+
logger.break();
|
|
1591
|
+
for (const [pkg, range] of Object.entries(diff.added)) {
|
|
1592
|
+
console.log(` ${chalk9.green("+")} ${pkg} ${chalk9.dim(range)}`);
|
|
1593
|
+
}
|
|
1594
|
+
for (const [pkg, { from, to }] of Object.entries(diff.changed)) {
|
|
1595
|
+
console.log(
|
|
1596
|
+
` ${chalk9.yellow("~")} ${pkg} ${chalk9.dim(from)} \u2192 ${chalk9.dim(to)}`
|
|
1597
|
+
);
|
|
1598
|
+
}
|
|
1599
|
+
for (const pkg of diff.removed) {
|
|
1600
|
+
console.log(` ${chalk9.red("-")} ${pkg}`);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
const projectDeps = getProjectDeps(cwd);
|
|
1604
|
+
const toInstall = ["@rootnative/core"];
|
|
1605
|
+
const optionalMeta = latest.peerDependenciesMeta ?? {};
|
|
1606
|
+
for (const [pkg] of Object.entries(diff.added)) {
|
|
1607
|
+
const isOptional = optionalMeta[pkg]?.optional === true;
|
|
1608
|
+
if (!isOptional && !projectDeps[pkg]) {
|
|
1609
|
+
toInstall.push(pkg);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
const latestPeers = latest.peerDependencies ?? {};
|
|
1613
|
+
for (const [pkg] of Object.entries(latestPeers)) {
|
|
1614
|
+
const isOptional = optionalMeta[pkg]?.optional === true;
|
|
1615
|
+
if (!isOptional && !projectDeps[pkg] && !toInstall.includes(pkg)) {
|
|
1616
|
+
toInstall.push(pkg);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
logger.break();
|
|
1620
|
+
console.log(chalk9.bold("Upgrade plan:"));
|
|
1621
|
+
logger.break();
|
|
1622
|
+
console.log(
|
|
1623
|
+
` ${chalk9.cyan("upgrade")} @rootnative/core ${chalk9.dim(`v${installed.version}`)} \u2192 ${chalk9.green(`v${latest.version}`)}`
|
|
1624
|
+
);
|
|
1625
|
+
const newDeps = toInstall.filter((p) => p !== "@rootnative/core");
|
|
1626
|
+
if (newDeps.length > 0) {
|
|
1627
|
+
for (const pkg of newDeps) {
|
|
1628
|
+
console.log(
|
|
1629
|
+
` ${chalk9.green("install")} ${pkg} ${chalk9.dim("(new peer dependency)")}`
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
const missingOptional = [];
|
|
1634
|
+
for (const [pkg] of Object.entries(latestPeers)) {
|
|
1635
|
+
const isOptional = optionalMeta[pkg]?.optional === true;
|
|
1636
|
+
if (isOptional && !projectDeps[pkg]) {
|
|
1637
|
+
missingOptional.push(pkg);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
if (missingOptional.length > 0) {
|
|
1641
|
+
logger.break();
|
|
1642
|
+
logger.info(
|
|
1643
|
+
`Optional peer dependencies not installed: ${chalk9.dim(missingOptional.join(", "))}`
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
logger.break();
|
|
1647
|
+
if (!options.yes) {
|
|
1648
|
+
const { proceed } = await prompts5({
|
|
1649
|
+
type: "confirm",
|
|
1650
|
+
name: "proceed",
|
|
1651
|
+
message: "Proceed with upgrade?",
|
|
1652
|
+
initial: true
|
|
1653
|
+
});
|
|
1654
|
+
if (!proceed) {
|
|
1655
|
+
logger.info("Upgrade cancelled.");
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
const pm = options.packageManager ?? project.packageManager;
|
|
1660
|
+
const command = getInstallCommand(pm, toInstall);
|
|
1661
|
+
const [cmd, ...args] = command.split(" ");
|
|
1662
|
+
logger.break();
|
|
1663
|
+
logger.info("Upgrading @rootnative/core...");
|
|
1664
|
+
logger.break();
|
|
1665
|
+
try {
|
|
1666
|
+
await execa4(cmd, args, { cwd, stdio: "inherit" });
|
|
1667
|
+
logger.break();
|
|
1668
|
+
logger.success("Upgrade complete");
|
|
1669
|
+
} catch {
|
|
1670
|
+
logger.break();
|
|
1671
|
+
logger.error("Failed to upgrade");
|
|
1672
|
+
logger.error(`Run manually: ${chalk9.bold(command)}`);
|
|
1673
|
+
process.exit(1);
|
|
1674
|
+
}
|
|
1675
|
+
const updated = getInstalledPackageInfo(cwd, "@rootnative/core");
|
|
1676
|
+
logger.break();
|
|
1677
|
+
if (updated) {
|
|
1678
|
+
logger.success(
|
|
1679
|
+
`@rootnative/core upgraded to ${chalk9.bold(`v${updated.version}`)}`
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
if (newDeps.length > 0) {
|
|
1683
|
+
logger.success(`Installed ${newDeps.length} new peer dependency(s)`);
|
|
1684
|
+
}
|
|
1685
|
+
if (diff.removed.length > 0) {
|
|
1686
|
+
logger.break();
|
|
1687
|
+
logger.info(
|
|
1688
|
+
`The following peer dependencies are no longer required and can be removed:`
|
|
1689
|
+
);
|
|
1690
|
+
for (const pkg of diff.removed) {
|
|
1691
|
+
logger.info(` ${pkg}`);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
logger.break();
|
|
1695
|
+
if (options.all) {
|
|
1696
|
+
logger.info("Updating installed components...");
|
|
1697
|
+
await updateCommand([], cwd, { all: true, dryRun: false });
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
// src/lib/types.ts
|
|
1702
|
+
var PACKAGE_MANAGERS = ["npm", "yarn", "pnpm", "bun"];
|
|
1703
|
+
function isValidPackageManager(value) {
|
|
1704
|
+
return PACKAGE_MANAGERS.includes(value);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// src/index.ts
|
|
1708
|
+
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(
|
|
1711
|
+
"--package-manager <pm>",
|
|
1712
|
+
`Package manager to use (${PACKAGE_MANAGERS.join(", ")})`
|
|
1713
|
+
).action(async (name, options) => {
|
|
1714
|
+
try {
|
|
1715
|
+
if (options.packageManager) {
|
|
1716
|
+
validatePackageManager(options.packageManager);
|
|
1717
|
+
}
|
|
1718
|
+
await createCommand(name, {
|
|
1719
|
+
yes: options.yes,
|
|
1720
|
+
template: options.template,
|
|
1721
|
+
packageManager: options.packageManager
|
|
1722
|
+
});
|
|
1723
|
+
} catch (error) {
|
|
1724
|
+
handleError(error);
|
|
1725
|
+
}
|
|
1726
|
+
});
|
|
1727
|
+
program.command("init").description("Initialize your project for RootNative UI").option("-y, --yes", "Skip prompts and use defaults", false).option("--components-alias <alias>", "Components install path alias").option("--lib-alias <alias>", "Utility files path alias").option(
|
|
1728
|
+
"--package-manager <pm>",
|
|
1729
|
+
`Package manager to use (${PACKAGE_MANAGERS.join(", ")})`
|
|
1730
|
+
).action(async (options) => {
|
|
1731
|
+
try {
|
|
1732
|
+
if (options.packageManager) {
|
|
1733
|
+
validatePackageManager(options.packageManager);
|
|
1734
|
+
}
|
|
1735
|
+
await initCommand(process.cwd(), {
|
|
1736
|
+
yes: options.yes,
|
|
1737
|
+
componentsAlias: options.componentsAlias,
|
|
1738
|
+
libAlias: options.libAlias,
|
|
1739
|
+
packageManager: options.packageManager
|
|
1740
|
+
});
|
|
1741
|
+
} catch (error) {
|
|
1742
|
+
handleError(error);
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
1745
|
+
program.command("add").description("Add components to your project").argument("<components...>", "Component names to add").option("-f, --force", "Overwrite existing components", false).option(
|
|
1746
|
+
"-d, --dry-run",
|
|
1747
|
+
"Show what would be installed without making changes",
|
|
1748
|
+
false
|
|
1749
|
+
).option(
|
|
1750
|
+
"--package-manager <pm>",
|
|
1751
|
+
`Package manager to use (${PACKAGE_MANAGERS.join(", ")})`
|
|
1752
|
+
).action(async (components, options) => {
|
|
1753
|
+
try {
|
|
1754
|
+
if (options.packageManager) {
|
|
1755
|
+
validatePackageManager(options.packageManager);
|
|
1756
|
+
}
|
|
1757
|
+
await addCommand(components, process.cwd(), {
|
|
1758
|
+
force: options.force,
|
|
1759
|
+
dryRun: options.dryRun,
|
|
1760
|
+
packageManager: options.packageManager
|
|
1761
|
+
});
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
handleError(error);
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
program.command("update").description("Update installed components to the latest version").argument("[components...]", "Component names to update").option("-a, --all", "Update all installed components", false).option("-d, --dry-run", "Show diff without applying changes", false).action(async (components, options) => {
|
|
1767
|
+
try {
|
|
1768
|
+
await updateCommand(components, process.cwd(), {
|
|
1769
|
+
all: options.all,
|
|
1770
|
+
dryRun: options.dryRun
|
|
1771
|
+
});
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
handleError(error);
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
program.command("list").description("List available components").action(async () => {
|
|
1777
|
+
try {
|
|
1778
|
+
await listCommand(process.cwd());
|
|
1779
|
+
} catch (error) {
|
|
1780
|
+
handleError(error);
|
|
1781
|
+
}
|
|
1782
|
+
});
|
|
1783
|
+
program.command("doctor").description("Check your project for issues").action(async () => {
|
|
1784
|
+
try {
|
|
1785
|
+
await doctorCommand(process.cwd());
|
|
1786
|
+
} catch (error) {
|
|
1787
|
+
handleError(error);
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
program.command("upgrade").description("Upgrade @rootnative/core and install new peer dependencies").option("-y, --yes", "Skip confirmation prompt", false).option("-a, --all", "Also update all installed component files", false).option(
|
|
1791
|
+
"--package-manager <pm>",
|
|
1792
|
+
`Package manager to use (${PACKAGE_MANAGERS.join(", ")})`
|
|
1793
|
+
).action(async (options) => {
|
|
1794
|
+
try {
|
|
1795
|
+
if (options.packageManager) {
|
|
1796
|
+
validatePackageManager(options.packageManager);
|
|
1797
|
+
}
|
|
1798
|
+
await upgradeCommand(process.cwd(), {
|
|
1799
|
+
yes: options.yes,
|
|
1800
|
+
all: options.all,
|
|
1801
|
+
packageManager: options.packageManager
|
|
1802
|
+
});
|
|
1803
|
+
} catch (error) {
|
|
1804
|
+
handleError(error);
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
function validatePackageManager(value) {
|
|
1808
|
+
if (!isValidPackageManager(value)) {
|
|
1809
|
+
logger.error(
|
|
1810
|
+
`Unknown package manager "${value}". Available: ${PACKAGE_MANAGERS.join(", ")}`
|
|
1811
|
+
);
|
|
1812
|
+
process.exit(1);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
function handleError(error) {
|
|
1816
|
+
if (error instanceof Error) {
|
|
1817
|
+
logger.error(error.message);
|
|
1818
|
+
} else {
|
|
1819
|
+
logger.error("An unexpected error occurred.");
|
|
1820
|
+
}
|
|
1821
|
+
process.exit(1);
|
|
1822
|
+
}
|
|
1823
|
+
program.parse();
|