nativeui-cli 1.0.0-beta.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kishan Agarwal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # nativeui-cli
2
+
3
+ <p align="center">
4
+ <a href="https://www.npmjs.com/package/nativeui-cli">
5
+ <img src="https://img.shields.io/npm/v/nativeui-cli" alt="npm version" />
6
+ </a>
7
+ <a href="https://www.npmjs.com/package/nativeui-cli">
8
+ <img src="https://img.shields.io/npm/dm/nativeui-cli" alt="downloads" />
9
+ </a>
10
+ <a href="./LICENSE">
11
+ <img src="https://img.shields.io/npm/l/nativeui-cli" alt="license" />
12
+ </a>
13
+ <img src="https://img.shields.io/badge/Expo-Compatible-000000" alt="expo" />
14
+ <a>
15
+ <img src="https://img.shields.io/badge/TypeScript-4.9.5-blue" alt="typescript" />
16
+ </a>
17
+ <a href="https://nativeui.qzz.io">
18
+ <img src= "https://img.shields.io/badge/docs-online-blue" alt="docs" />
19
+ </a>
20
+ </p>
21
+
22
+ The CLI for Native UI β€” a collection of beautiful, native-feeling React Native and Expo components that you fully own.
23
+ The CLI for Native UI β€” a collection of beautiful, native-feeling React Native and Expo components that you fully own.
24
+
25
+ Unlike traditional component libraries, Native UI copies component source code directly into your project. No black boxes, no lock-in, and no fighting library abstractions.
26
+
27
+ ## Features
28
+
29
+ * 🎨 Native-feeling UI components for Expo and React Native
30
+ * πŸ“¦ Copy components directly into your codebase
31
+ * πŸŒ— Built-in dark mode support
32
+ * ⚑ Reanimated-powered interactions and animations
33
+ * β™Ώ Accessible by default
34
+ * πŸ”§ Fully customizable source code
35
+ * 🚫 No vendor lock-in
36
+
37
+ ---
38
+
39
+ ## Quick Start
40
+
41
+ Initialize Native UI in your project:
42
+
43
+ ```bash
44
+ npx nativeui-cli@latest init
45
+ ```
46
+
47
+ Then add components:
48
+
49
+ ```bash
50
+ npx nativeui-cli@latest add button typography
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Commands
56
+
57
+ ### init
58
+
59
+ Set up Native UI in your project.
60
+
61
+ ```bash
62
+ nativeui-cli init
63
+ ```
64
+
65
+ Creates:
66
+
67
+ * `native-ui.json`
68
+ * Theme utilities
69
+ * Required dependencies
70
+ * Project configuration
71
+
72
+ #### Options
73
+
74
+ | Flag | Description |
75
+ | ------------- | -------------------------------- |
76
+ | `-y, --yes` | Skip prompts and use defaults |
77
+ | `-f, --force` | Overwrite existing configuration |
78
+
79
+ ---
80
+
81
+ ### add
82
+
83
+ Add one or more components.
84
+
85
+ ```bash
86
+ nativeui-cli add button
87
+ ```
88
+
89
+ ```bash
90
+ nativeui-cli add button input card
91
+ ```
92
+
93
+ ```bash
94
+ nativeui-cli add --all
95
+ ```
96
+
97
+ #### Options
98
+
99
+ | Flag | Description |
100
+ | ----------------- | -------------------------------- |
101
+ | `-o, --overwrite` | Overwrite existing files |
102
+ | `-a, --all` | Install all available components |
103
+
104
+ ---
105
+
106
+ ### remove
107
+
108
+ Remove installed components.
109
+
110
+ ```bash
111
+ nativeui-cli remove button
112
+ ```
113
+
114
+ Alias:
115
+
116
+ ```bash
117
+ nativeui-cli rm button
118
+ ```
119
+
120
+ ---
121
+
122
+ ### list
123
+
124
+ List available and installed components.
125
+
126
+ ```bash
127
+ nativeui-cli list
128
+ ```
129
+
130
+ Alias:
131
+
132
+ ```bash
133
+ nativeui-cli ls
134
+ ```
135
+
136
+ ---
137
+
138
+ ### diff
139
+
140
+ Compare your local components against the latest registry versions.
141
+
142
+ ```bash
143
+ nativeui-cli diff
144
+ ```
145
+
146
+ ```bash
147
+ nativeui-cli diff button
148
+ ```
149
+
150
+ Useful when new component updates are released and you've customized local copies.
151
+
152
+ ---
153
+
154
+ ## Example
155
+
156
+ ```bash
157
+ # Initialize project
158
+ npx nativeui-cli@latest init
159
+
160
+ # Add components
161
+ npx nativeui-cli@latest add button card input
162
+
163
+ # Check installed components
164
+ npx nativeui-cli@latest list
165
+
166
+ # See available updates
167
+ npx nativeui-cli@latest diff
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Philosophy
173
+
174
+ Native UI follows the same philosophy that made shadcn/ui popular:
175
+
176
+ **You own the code.**
177
+
178
+ Components are copied into your project as TypeScript source files. Edit them, refactor them, or delete them entirely. The CLI helps you get started, but your code remains yours.
179
+
180
+ ---
181
+
182
+ ## Documentation
183
+
184
+ Visit the documentation site for installation guides, component documentation, and examples.
185
+
186
+ [https://nativeui.qzz.io](https://nativeui.qzz.io)
187
+
188
+ ---
189
+
190
+ ## License
191
+
192
+ MIT
@@ -0,0 +1,4 @@
1
+ export declare function addCommand(componentArgs: string[], opts: {
2
+ overwrite?: boolean;
3
+ all?: boolean;
4
+ }): Promise<void>;
@@ -0,0 +1,166 @@
1
+ // src/commands/add.ts
2
+ // ─────────────────────────────────────────────────────────────
3
+ // `native-ui add [components...]`
4
+ //
5
+ // Available components come from the static COMPONENTS array in
6
+ // registry.ts β€” no network call needed just to show the list.
7
+ //
8
+ // File contents are fetched from the GraphQL backend only after
9
+ // the user has picked what they want.
10
+ //
11
+ // native-ui.json `components` is updated after each successful write.
12
+ // ─────────────────────────────────────────────────────────────
13
+ import { intro, outro, multiselect, confirm, spinner, log, note, } from "@clack/prompts";
14
+ import pc from "picocolors";
15
+ import { COMPONENTS, entryName } from "../registry.js";
16
+ import { readConfig, markInstalled, configExists } from "../config.js";
17
+ import { fetchRegistryClosure, writeFile, buildInstallCmd, installPackages, logError, logWarn, getMissingPackages, resolveComponentDest, } from "../utils.js";
18
+ export async function addCommand(componentArgs, opts) {
19
+ intro(pc.bgCyan(pc.black(" native-ui ")) + pc.dim(" add components"));
20
+ if (!configExists()) {
21
+ logError("native-ui.json not found. Run `nativeui-cli init` first.");
22
+ process.exit(1);
23
+ }
24
+ const config = readConfig();
25
+ const installedKeys = config.components; // what's already in native-ui.json
26
+ // ── Resolve which components the user wants ──────────────────
27
+ let selectedKeys;
28
+ if (opts.all) {
29
+ selectedKeys = [...COMPONENTS];
30
+ }
31
+ else if (componentArgs.length > 0) {
32
+ selectedKeys = componentArgs.map((c) => c.toLowerCase());
33
+ const unknown = selectedKeys.filter((k) => !COMPONENTS.includes(k));
34
+ if (unknown.length > 0) {
35
+ logError(`Unknown component(s): ${unknown.join(", ")}`);
36
+ log.info(`Run ${pc.bold("nativeui-cli list")} to see all available components.`);
37
+ process.exit(1);
38
+ }
39
+ }
40
+ else {
41
+ // ── Interactive multiselect β€” list comes from COMPONENTS ───
42
+ const SELECT_ALL = "__select_all__";
43
+ const options = [
44
+ { value: SELECT_ALL, label: pc.bold("Select all") },
45
+ ...[...COMPONENTS].sort().map((key) => {
46
+ const isInstalled = installedKeys.includes(key);
47
+ return {
48
+ value: key,
49
+ label: pc.bold(key.padEnd(28)) +
50
+ (isInstalled ? pc.green("installed") : ""),
51
+ };
52
+ }),
53
+ ];
54
+ const selected = await multiselect({
55
+ message: "Pick the components to add:",
56
+ options,
57
+ required: true,
58
+ });
59
+ if (typeof selected === "symbol") {
60
+ log.info("Cancelled.");
61
+ process.exit(0);
62
+ }
63
+ const chosen = selected;
64
+ selectedKeys = chosen.includes(SELECT_ALL) ? [...COMPONENTS] : chosen;
65
+ }
66
+ // ── Overwrite check ──────────────────────────────────────────
67
+ const alreadyInstalled = selectedKeys.filter((k) => installedKeys.includes(k));
68
+ if (alreadyInstalled.length > 0 && !opts.overwrite) {
69
+ logWarn(`Already installed: ${alreadyInstalled.join(", ")}`);
70
+ const overwrite = await confirm({
71
+ message: "Overwrite existing files?",
72
+ initialValue: false,
73
+ });
74
+ if (typeof overwrite === "symbol" || !overwrite) {
75
+ selectedKeys = selectedKeys.filter((k) => !alreadyInstalled.includes(k));
76
+ }
77
+ }
78
+ if (selectedKeys.length === 0) {
79
+ log.info("Nothing to install.");
80
+ process.exit(0);
81
+ }
82
+ // ── Fetch file contents + resolve transitive deps ────────────
83
+ const fetchSpin = spinner();
84
+ fetchSpin.start("Fetching components from registry…");
85
+ let closureEntries;
86
+ try {
87
+ closureEntries = await fetchRegistryClosure(selectedKeys);
88
+ fetchSpin.stop("");
89
+ }
90
+ catch (err) {
91
+ fetchSpin.stop(pc.red("Fetch failed."));
92
+ logError(err.message);
93
+ process.exit(1);
94
+ }
95
+ const entryMap = new Map(closureEntries.map((e) => [e.key.toLowerCase(), e]));
96
+ const resolvedKeys = closureEntries.map((e) => e.key.toLowerCase());
97
+ // Show auto-added transitive deps
98
+ const addedDeps = resolvedKeys.filter((k) => !selectedKeys.includes(k));
99
+ if (addedDeps.length > 0) {
100
+ note(addedDeps
101
+ .map((k) => ` ${pc.dim("+")} ${entryName(entryMap.get(k))}`)
102
+ .join("\n"), "Also adding required dependencies");
103
+ }
104
+ // ── Write files ───────────────────────────────────────────────
105
+ const allNpmDeps = new Set();
106
+ const results = [];
107
+ for (const key of resolvedKeys) {
108
+ const entry = entryMap.get(key);
109
+ const file = entry.files?.[0];
110
+ const content = file?.content;
111
+ if (typeof content !== "string") {
112
+ results.push({
113
+ key,
114
+ dest: "",
115
+ ok: false,
116
+ err: "No file content in registry response.",
117
+ });
118
+ continue;
119
+ }
120
+ const dest = resolveComponentDest(file, config, key);
121
+ try {
122
+ writeFile(dest, content);
123
+ (entry.dependencies ?? []).forEach((d) => allNpmDeps.add(d));
124
+ markInstalled(key); // ← writes key into native-ui.json `components[]`
125
+ results.push({ key, dest, ok: true });
126
+ }
127
+ catch (err) {
128
+ results.push({ key, dest, ok: false, err: String(err) });
129
+ }
130
+ }
131
+ // ── Print results (shadcn style: name β†’ path) ─────────────────
132
+ for (const r of results) {
133
+ const name = entryName(entryMap.get(r.key) ?? { key: r.key });
134
+ if (r.ok) {
135
+ log.success(`${pc.green(name)} β†’ ${pc.dim(r.dest)}`);
136
+ }
137
+ else {
138
+ log.error(`${pc.red(name)}: ${r.err}`);
139
+ }
140
+ }
141
+ // ── Install npm deps ──────────────────────────────────────────
142
+ if (allNpmDeps.size > 0) {
143
+ const depList = [...allNpmDeps];
144
+ // Filter out packages already present in package.json
145
+ const missingDeps = getMissingPackages(depList, process.cwd());
146
+ if (missingDeps.length === 0) {
147
+ log.success(pc.green("All required dependencies are already installed."));
148
+ }
149
+ else {
150
+ const ds = spinner();
151
+ ds.start(`Installing ${missingDeps.join(", ")}…`);
152
+ const ok = await installPackages(missingDeps, config.expoRunner, process.cwd(), (pkg, i, total) => log.message(pc.dim(`Installing ${pkg} (${i + 1}/${total})…`)));
153
+ if (ok) {
154
+ ds.stop(pc.green(`Installed ${missingDeps.join(", ")}`));
155
+ }
156
+ else {
157
+ ds.stop(pc.yellow("Dependency install failed. Run manually:"));
158
+ log.message(pc.dim(` ${buildInstallCmd(config.expoRunner, missingDeps)}`));
159
+ }
160
+ }
161
+ }
162
+ const failed = results.filter((r) => !r.ok);
163
+ outro(failed.length === 0
164
+ ? pc.cyan(`${results.length} component(s) added successfully.`)
165
+ : pc.yellow(`Done with ${failed.length} error(s).`));
166
+ }
@@ -0,0 +1 @@
1
+ export declare function diffCommand(componentArg?: string): Promise<void>;
@@ -0,0 +1,133 @@
1
+ // src/commands/diff.ts
2
+ // ─────────────────────────────────────────────────────────────
3
+ // `native-ui diff [component]`
4
+ //
5
+ // Compares installed component files against the registry.
6
+ // "Installed" = `native-ui.json β†’ components[]`
7
+ // Available component keys come from the static COMPONENTS array.
8
+ // ─────────────────────────────────────────────────────────────
9
+ import { intro, outro, spinner, log, select } from "@clack/prompts";
10
+ import pc from "picocolors";
11
+ import { configExists, readConfig } from "../config.js";
12
+ import { fetchRegistryEntries, readFileSafe, isUpToDate, lineDiff, logError, logSuccess, logWarn, resolveComponentDest, } from "../utils.js";
13
+ import { COMPONENTS, entryName } from "../registry.js";
14
+ export async function diffCommand(componentArg) {
15
+ intro(pc.bgCyan(pc.black(" native-ui ")) + pc.dim(" diff"));
16
+ if (!configExists()) {
17
+ logError("native-ui.json not found. Run `nativeui-cli init` first.");
18
+ process.exit(1);
19
+ }
20
+ const config = readConfig();
21
+ // ── Decide which components to check ────────────────────────
22
+ let keysToCheck;
23
+ if (componentArg) {
24
+ const key = componentArg.toLowerCase();
25
+ if (!COMPONENTS.includes(key)) {
26
+ logError(`Unknown component: ${componentArg}. Run \`nativeui-cli list\` to see available components.`);
27
+ process.exit(1);
28
+ }
29
+ if (!config.components.includes(key)) {
30
+ logError(`${componentArg} is not installed. Run \`nativeui-cli add ${key}\` first.`);
31
+ process.exit(1);
32
+ }
33
+ keysToCheck = [key];
34
+ }
35
+ else {
36
+ keysToCheck = config.components; // everything in native-ui.json
37
+ if (keysToCheck.length === 0) {
38
+ log.info("No components installed yet. Run `nativeui-cli add` first.");
39
+ process.exit(0);
40
+ }
41
+ }
42
+ // ── Fetch remote versions ────────────────────────────────────
43
+ const s = spinner();
44
+ s.start(`Fetching ${keysToCheck.length} component(s) from registry…`);
45
+ let registryEntries;
46
+ try {
47
+ registryEntries = await fetchRegistryEntries(keysToCheck);
48
+ s.stop("");
49
+ }
50
+ catch (err) {
51
+ s.stop(pc.red("Fetch failed."));
52
+ logError(err.message);
53
+ process.exit(1);
54
+ }
55
+ const entryMap = new Map(registryEntries.map((e) => [e.key.toLowerCase(), e]));
56
+ const results = [];
57
+ for (const key of keysToCheck) {
58
+ const entry = entryMap.get(key);
59
+ if (!entry) {
60
+ results.push({ key, status: "error", err: "Not found in registry." });
61
+ continue;
62
+ }
63
+ const file = entry.files?.[0];
64
+ const localPath = resolveComponentDest(file, config, key);
65
+ const localContent = readFileSafe(localPath);
66
+ if (localContent === null) {
67
+ results.push({ key, status: "missing" });
68
+ continue;
69
+ }
70
+ const remoteContent = file?.content;
71
+ if (typeof remoteContent !== "string") {
72
+ results.push({
73
+ key,
74
+ status: "error",
75
+ err: "Registry returned no file content.",
76
+ });
77
+ continue;
78
+ }
79
+ results.push(isUpToDate(localContent, remoteContent)
80
+ ? { key, status: "upToDate" }
81
+ : {
82
+ key,
83
+ status: "changed",
84
+ diff: lineDiff(localContent, remoteContent),
85
+ });
86
+ }
87
+ // ── Print results ─────────────────────────────────────────────
88
+ console.log();
89
+ for (const r of results) {
90
+ const name = entryName(entryMap.get(r.key) ?? { key: r.key });
91
+ if (r.status === "upToDate")
92
+ logSuccess(`${name} β€” up to date`);
93
+ if (r.status === "missing")
94
+ logWarn(`${name} β€” local file missing. Re-add: nativeui-cli add ${r.key}`);
95
+ if (r.status === "error")
96
+ logError(`${name} β€” ${r.err}`);
97
+ }
98
+ const changed = results.filter((r) => r.status === "changed");
99
+ if (changed.length === 0) {
100
+ console.log();
101
+ log.info(pc.green("All components are up to date!"));
102
+ }
103
+ else {
104
+ console.log();
105
+ log.warn(pc.yellow(`${changed.length} component(s) have updates available:`));
106
+ console.log();
107
+ for (const r of changed) {
108
+ const entry = entryMap.get(r.key);
109
+ const file = entry?.files?.[0];
110
+ console.log(pc.bold(`── ${entryName(entry ?? { key: r.key })} `) +
111
+ pc.dim(`(${file?.target ?? file?.path ?? r.key})`) +
112
+ pc.bold(" ──"));
113
+ console.log(r.diff);
114
+ console.log();
115
+ }
116
+ const choice = await select({
117
+ message: "What would you like to do?",
118
+ options: [
119
+ {
120
+ value: "update-all",
121
+ label: `Update all ${changed.length} component(s)`,
122
+ },
123
+ { value: "skip", label: "Skip β€” I will update manually" },
124
+ ],
125
+ });
126
+ if (choice === "update-all") {
127
+ const { addCommand } = await import("./add.js");
128
+ await addCommand(changed.map((r) => r.key), { overwrite: true });
129
+ return;
130
+ }
131
+ }
132
+ outro(pc.cyan("Diff complete."));
133
+ }
@@ -0,0 +1,4 @@
1
+ export declare function initCommand(opts: {
2
+ yes?: boolean;
3
+ force?: boolean;
4
+ }): Promise<void>;
@@ -0,0 +1,133 @@
1
+ // src/commands/init.ts
2
+ // ─────────────────────────────────────────────────────────────
3
+ // `native-ui init`
4
+ //
5
+ // Creates native-ui.json with project settings.
6
+ // No registry fetch needed β€” component list is static in registry.ts.
7
+ // ─────────────────────────────────────────────────────────────
8
+ import { intro, outro, confirm, select, text, spinner, log, note, } from '@clack/prompts';
9
+ import pc from 'picocolors';
10
+ import path from 'path';
11
+ import { configExists, readConfig, writeConfig, markInstalled, DEFAULT_CONFIG, DEFAULT_BASE_DEPENDENCIES, } from '../config.js';
12
+ import { fetchRegistryEntries, detectExpoRunner, buildInstallCmd, installPackages, isExpoProject, logWarn, writeFile, getMissingPackages, // <-- ADDED THIS
13
+ } from '../utils.js';
14
+ import { COMPONENTS } from '../registry.js';
15
+ export async function initCommand(opts) {
16
+ intro(pc.bgCyan(pc.black(' native-ui ')) + pc.dim(' initialise project'));
17
+ const projectRoot = process.cwd();
18
+ // ── Already initialised? ────────────────────────────────────
19
+ if (configExists() && !opts.force) {
20
+ const overwrite = await confirm({
21
+ message: 'native-ui.json already exists. Overwrite it?',
22
+ initialValue: false,
23
+ });
24
+ if (!overwrite || typeof overwrite === 'symbol') {
25
+ log.info('Init cancelled β€” existing config preserved.');
26
+ process.exit(0);
27
+ }
28
+ }
29
+ // ── Expo project check ──────────────────────────────────────
30
+ if (!isExpoProject()) {
31
+ logWarn('No app.json found β€” this does not look like an Expo project.');
32
+ const proceed = await confirm({ message: 'Continue anyway?', initialValue: false });
33
+ if (!proceed || typeof proceed === 'symbol')
34
+ process.exit(0);
35
+ }
36
+ // ── Preserve already-installed components across re-init ─────
37
+ let existingComponents = [];
38
+ if (configExists()) {
39
+ try {
40
+ existingComponents = readConfig().components;
41
+ }
42
+ catch { /* fresh install */ }
43
+ }
44
+ let config = { ...DEFAULT_CONFIG, components: existingComponents };
45
+ if (!opts.yes) {
46
+ const useTs = await confirm({ message: 'Are you using TypeScript?', initialValue: true });
47
+ if (typeof useTs === 'symbol')
48
+ process.exit(0);
49
+ config.typescript = useTs;
50
+ const outputDir = await text({
51
+ message: 'Where should components be installed?',
52
+ initialValue: config.outputDir,
53
+ placeholder: 'components/ui',
54
+ validate: (v) => (!v || !v.trim() ? 'Path cannot be empty.' : undefined),
55
+ });
56
+ if (typeof outputDir === 'symbol')
57
+ process.exit(0);
58
+ config.outputDir = outputDir.trim();
59
+ const runner = await select({
60
+ message: 'Which command runs Expo?',
61
+ options: [
62
+ { value: 'npx', label: 'npx expo (npm)' },
63
+ { value: 'pnpm', label: 'pnpm expo' },
64
+ { value: 'yarn', label: 'yarn expo' },
65
+ { value: 'bunx', label: 'bunx expo' },
66
+ ],
67
+ initialValue: detectExpoRunner(),
68
+ });
69
+ if (typeof runner === 'symbol')
70
+ process.exit(0);
71
+ config.expoRunner = runner;
72
+ }
73
+ config.baseDependencies = [...DEFAULT_BASE_DEPENDENCIES];
74
+ // ── Write native-ui.json ─────────────────────────────────────
75
+ writeConfig(config);
76
+ // ── Summary ──────────────────────────────────────────────────
77
+ note([
78
+ `${pc.dim('config')} native-ui.json`,
79
+ `${pc.dim('typescript')} ${config.typescript ? 'yes' : 'no'}`,
80
+ `${pc.dim('outputDir')} ${config.outputDir}`,
81
+ `${pc.dim('expoRunner')} ${config.expoRunner}`,
82
+ `${pc.dim('baseDependencies')} ${config.baseDependencies.join(', ')}`,
83
+ `${pc.dim('components')} ${COMPONENTS.length} available`,
84
+ ].join('\n'), 'native-ui.json');
85
+ // ── Install base peer deps ───────────────────────────────────
86
+ let shouldInstall = true;
87
+ if (!opts.yes) {
88
+ shouldInstall = await confirm({
89
+ message: `Install base dependencies (${config.baseDependencies.join(', ')})?`,
90
+ initialValue: true,
91
+ });
92
+ // FIX: Properly handle cancellation at the prompt
93
+ if (typeof shouldInstall === 'symbol') {
94
+ log.info('Cancelled.');
95
+ process.exit(0);
96
+ }
97
+ }
98
+ if (shouldInstall) {
99
+ // FIX: Filter base dependencies to skip already installed packages
100
+ const missingBaseDeps = getMissingPackages(config.baseDependencies, projectRoot);
101
+ if (missingBaseDeps.length === 0) {
102
+ log.success(pc.green('Base dependencies are already installed.'));
103
+ }
104
+ else {
105
+ const s = spinner();
106
+ s.start(`Installing base dependencies…`);
107
+ log.message(pc.dim(`Installing ${missingBaseDeps.join(', ')}…`));
108
+ const ok = installPackages(missingBaseDeps, config.expoRunner, projectRoot);
109
+ if (ok) {
110
+ s.stop(pc.green('Base dependencies installed.'));
111
+ }
112
+ else {
113
+ s.stop(pc.yellow('Install failed β€” run manually:'));
114
+ log.message(pc.dim(` ${buildInstallCmd(config.expoRunner, missingBaseDeps)}`));
115
+ }
116
+ }
117
+ }
118
+ // ── Write shared lib helpers (theme / use-styles) ────────────
119
+ try {
120
+ const libEntries = await fetchRegistryEntries(['lib-theme', 'lib-use-styles']);
121
+ for (const entry of libEntries) {
122
+ const file = entry.files?.[0];
123
+ if (!file || typeof file.content !== 'string')
124
+ continue;
125
+ const dest = path.resolve(projectRoot, file.target ?? file.path ?? entry.key);
126
+ writeFile(dest, file.content);
127
+ markInstalled(entry.key.toLowerCase());
128
+ log.message(pc.dim(`Wrote ${file.target ?? file.path ?? entry.key}`));
129
+ }
130
+ }
131
+ catch { /* non-fatal */ }
132
+ outro(pc.cyan('Done! Run ') + pc.bold('nativeui-cli add') + pc.cyan(' to add components.'));
133
+ }
@@ -0,0 +1,5 @@
1
+ interface ListOptions {
2
+ category?: string;
3
+ }
4
+ export declare function listCommand(opts: ListOptions): Promise<void>;
5
+ export {};