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 +21 -0
- package/README.md +192 -0
- package/dist/commands/add.d.ts +4 -0
- package/dist/commands/add.js +166 -0
- package/dist/commands/diff.d.ts +1 -0
- package/dist/commands/diff.js +133 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +133 -0
- package/dist/commands/list.d.ts +5 -0
- package/dist/commands/list.js +44 -0
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +97 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.js +115 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +84 -0
- package/dist/registry.d.ts +19 -0
- package/dist/registry.js +53 -0
- package/dist/utils.d.ts +70 -0
- package/dist/utils.js +320 -0
- package/package.json +56 -0
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,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,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
|
+
}
|