expo-app-ui 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -30
- package/bin/expo-app-ui.js +73 -409
- package/package.json +14 -5
- package/src/commands/add.js +500 -0
- package/src/commands/list.js +100 -0
- package/src/core/dependencyDetector.js +114 -0
- package/src/core/templateProcessor.js +93 -0
- package/src/utils/config.js +62 -0
- package/src/utils/errors.js +41 -0
- package/src/utils/logger.js +52 -0
- package/src/utils/pathUtils.js +120 -0
- package/src/utils/prompt.js +42 -0
- package/templates/LoadingBar.tsx +70 -0
- package/templates/components/ui/loading-bar.tsx +70 -0
- package/templates/context/top-loading-bar-context.tsx +39 -0
- /package/templates/{box-view.tsx → components/ui/box-view.tsx} +0 -0
- /package/templates/{button.tsx → components/ui/button.tsx} +0 -0
- /package/templates/{custom-modal.tsx → components/ui/custom-modal.tsx} +0 -0
- /package/templates/{custom-text.tsx → components/ui/custom-text.tsx} +0 -0
- /package/templates/{marquee.tsx → components/ui/marquee.tsx} +0 -0
- /package/templates/{otp-input.tsx → components/ui/otp-input.tsx} +0 -0
- /package/templates/{profile-pic.tsx → components/ui/profile-pic.tsx} +0 -0
- /package/templates/{progress-bar.tsx → components/ui/progress-bar.tsx} +0 -0
package/README.md
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
# Expo UI
|
|
1
|
+
# Expo App UI
|
|
2
2
|
|
|
3
3
|
A UI component library for Expo React Native. Copy components directly into your project and customize them to your needs.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 📚 Documentation
|
|
6
|
+
|
|
7
|
+
**👉 [View Full Documentation →](https://expo-apps-ui.vercel.app)**
|
|
8
|
+
|
|
9
|
+
For complete documentation, usage examples, API references, and detailed instructions, visit our documentation site:
|
|
10
|
+
|
|
11
|
+
**https://expo-apps-ui.vercel.app**
|
|
12
|
+
|
|
13
|
+
The documentation includes:
|
|
14
|
+
- 📖 Getting Started Guide
|
|
15
|
+
- 🎨 Component Documentation
|
|
16
|
+
- 🛠️ CLI Commands Reference
|
|
17
|
+
- 💡 Usage Examples
|
|
18
|
+
- 🔧 Configuration Guides
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Installation
|
|
6
23
|
|
|
7
24
|
You can use this library directly with npx:
|
|
8
25
|
|
|
@@ -26,14 +43,14 @@ expo-app-ui add <component-name>
|
|
|
26
43
|
|
|
27
44
|
### Adding Components
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
Add a component to your project:
|
|
30
47
|
|
|
31
48
|
```bash
|
|
32
49
|
npx expo-app-ui add custom-text
|
|
33
50
|
```
|
|
34
51
|
|
|
35
52
|
This will:
|
|
36
|
-
- Copy the component template to `components/ui/
|
|
53
|
+
- Copy the component template to `components/ui/custom-text.tsx` in your project
|
|
37
54
|
- Automatically detect and add required dependencies (helpers, constants)
|
|
38
55
|
- Preserve all imports and dependencies
|
|
39
56
|
- Allow you to customize the component as needed
|
|
@@ -44,44 +61,33 @@ npx expo-app-ui add button
|
|
|
44
61
|
# Automatically adds normalizeSize helper and theme constants if needed
|
|
45
62
|
```
|
|
46
63
|
|
|
47
|
-
### Adding Helpers
|
|
48
|
-
|
|
49
|
-
To add a helper utility:
|
|
64
|
+
### Adding Helpers, Constants, and Contexts
|
|
50
65
|
|
|
51
66
|
```bash
|
|
67
|
+
# Add a helper
|
|
52
68
|
npx expo-app-ui add normalizeSize
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
This will copy the helper to `helpers/normalizeSize.ts` in your project.
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
To add constants (like theme):
|
|
60
|
-
|
|
61
|
-
```bash
|
|
70
|
+
# Add constants
|
|
62
71
|
npx expo-app-ui add theme
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
This will copy the constants to `constants/theme.ts` in your project.
|
|
66
72
|
|
|
67
|
-
|
|
73
|
+
# Add contexts (e.g., top loading bar)
|
|
74
|
+
npx expo-app-ui add top-loading-bar
|
|
75
|
+
```
|
|
68
76
|
|
|
69
77
|
### Listing Available Items
|
|
70
78
|
|
|
71
|
-
To see all available components, helpers, and constants:
|
|
72
|
-
|
|
73
79
|
```bash
|
|
74
80
|
npx expo-app-ui list
|
|
75
81
|
```
|
|
76
82
|
|
|
77
83
|
### Overwriting Existing Files
|
|
78
84
|
|
|
79
|
-
To replace an existing component, helper, or constant:
|
|
80
|
-
|
|
81
85
|
```bash
|
|
82
86
|
npx expo-app-ui add custom-text --overwrite
|
|
83
87
|
```
|
|
84
88
|
|
|
89
|
+
> 📖 **For detailed usage instructions, examples, and API documentation, visit [expo-apps-ui.vercel.app](https://expo-apps-ui.vercel.app)**
|
|
90
|
+
|
|
85
91
|
## Project Structure
|
|
86
92
|
|
|
87
93
|
After adding components, your project structure will look like:
|
|
@@ -105,9 +111,12 @@ your-project/
|
|
|
105
111
|
The CLI automatically detects when a component requires:
|
|
106
112
|
- `normalizeSize` helper (from `@/helper/normalizeSize`)
|
|
107
113
|
- `theme` constants (from `@/constants/theme`)
|
|
114
|
+
- Related components or contexts
|
|
108
115
|
|
|
109
116
|
When you add a component that uses these dependencies, they will be automatically added to your project.
|
|
110
117
|
|
|
118
|
+
> 📖 **Learn more about auto-dependency detection in the [documentation](https://expo-apps-ui.vercel.app/docs/cli)**
|
|
119
|
+
|
|
111
120
|
## Component Templates
|
|
112
121
|
|
|
113
122
|
Components are copied directly into your project, so you have full control:
|
|
@@ -119,12 +128,13 @@ Components are copied directly into your project, so you have full control:
|
|
|
119
128
|
|
|
120
129
|
## Path Aliases
|
|
121
130
|
|
|
122
|
-
Components use path aliases like `@/components/ui/
|
|
131
|
+
Components use path aliases like `@/components/ui/custom-text` and `@/constants/theme`. Make sure your Expo project has these configured:
|
|
123
132
|
|
|
124
133
|
**tsconfig.json:**
|
|
125
134
|
```json
|
|
126
135
|
{
|
|
127
136
|
"compilerOptions": {
|
|
137
|
+
"baseUrl": ".",
|
|
128
138
|
"paths": {
|
|
129
139
|
"@/*": ["./*"]
|
|
130
140
|
}
|
|
@@ -149,6 +159,8 @@ module.exports = {
|
|
|
149
159
|
};
|
|
150
160
|
```
|
|
151
161
|
|
|
162
|
+
> 📖 **See the [Getting Started guide](https://expo-apps-ui.vercel.app/docs/getting-started) for detailed setup instructions**
|
|
163
|
+
|
|
152
164
|
## Available Components
|
|
153
165
|
|
|
154
166
|
- `custom-text` - A customizable Text component with font, color, and spacing props
|
|
@@ -159,6 +171,11 @@ module.exports = {
|
|
|
159
171
|
- `progress-bar` - A progress bar component with variants
|
|
160
172
|
- `marquee` - A scrolling marquee component
|
|
161
173
|
- `otp-input` - An OTP input component
|
|
174
|
+
- `loading-bar` - An animated top loading bar component
|
|
175
|
+
|
|
176
|
+
## Available Contexts
|
|
177
|
+
|
|
178
|
+
- `top-loading-bar-context` - React Context for managing top loading bar state
|
|
162
179
|
|
|
163
180
|
## Available Helpers
|
|
164
181
|
|
|
@@ -168,15 +185,20 @@ module.exports = {
|
|
|
168
185
|
|
|
169
186
|
- `theme` - Theme constants including colors, fonts, and sizes
|
|
170
187
|
|
|
188
|
+
> 📖 **View complete documentation, props, examples, and usage for all components at [expo-apps-ui.vercel.app/docs/components](https://expo-apps-ui.vercel.app/docs/components)**
|
|
189
|
+
|
|
171
190
|
## Contributing
|
|
172
191
|
|
|
173
|
-
To add new components, helpers, or
|
|
192
|
+
To add new components, helpers, constants, or contexts:
|
|
193
|
+
|
|
194
|
+
1. **Components**: Create a new `.tsx` file in the `templates/components/ui/` directory
|
|
195
|
+
2. **Contexts**: Create a new `.tsx` file in the `templates/context/` directory
|
|
196
|
+
3. **Helpers**: Create a new `.ts` file in the `templates/helpers/` directory
|
|
197
|
+
4. **Constants**: Create a new `.ts` file in the `templates/constants/` directory
|
|
198
|
+
5. Use kebab-case for filenames (e.g., `my-component.tsx`)
|
|
199
|
+
6. The item will be automatically available via the CLI
|
|
174
200
|
|
|
175
|
-
|
|
176
|
-
2. **Helpers**: Create a new `.ts` file in the `templates/helpers/` directory
|
|
177
|
-
3. **Constants**: Create a new `.ts` file in the `templates/constants/` directory
|
|
178
|
-
4. Use kebab-case for filenames (e.g., `my-component.tsx`)
|
|
179
|
-
5. The item will be automatically available via the CLI
|
|
201
|
+
> 📖 **For contribution guidelines and best practices, visit the [documentation](https://expo-apps-ui.vercel.app)**
|
|
180
202
|
|
|
181
203
|
## License
|
|
182
204
|
|
package/bin/expo-app-ui.js
CHANGED
|
@@ -1,430 +1,94 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const getPackageDir = () => {
|
|
10
|
-
let currentDir = __dirname;
|
|
11
|
-
|
|
12
|
-
while (currentDir !== path.dirname(currentDir)) {
|
|
13
|
-
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
14
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
15
|
-
const pkg = fs.readJsonSync(packageJsonPath);
|
|
16
|
-
if (pkg.bin && (pkg.bin['expo-app-ui'] || pkg.name === 'expo-app-ui')) {
|
|
17
|
-
return currentDir;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
currentDir = path.dirname(currentDir);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return path.dirname(__dirname);
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const packageDir = getPackageDir();
|
|
27
|
-
const templatesDir = path.join(packageDir, 'templates');
|
|
28
|
-
|
|
29
|
-
// Get the current working directory (user's project)
|
|
30
|
-
const getProjectRoot = () => {
|
|
31
|
-
let currentDir = process.cwd();
|
|
32
|
-
|
|
33
|
-
while (currentDir !== path.dirname(currentDir)) {
|
|
34
|
-
if (fs.existsSync(path.join(currentDir, 'package.json')) ||
|
|
35
|
-
fs.existsSync(path.join(currentDir, 'app.json'))) {
|
|
36
|
-
return currentDir;
|
|
37
|
-
}
|
|
38
|
-
currentDir = path.dirname(currentDir);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return process.cwd();
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const projectRoot = getProjectRoot();
|
|
45
|
-
|
|
46
|
-
// Function to convert component name to file name
|
|
47
|
-
const toKebabCase = (str) => {
|
|
48
|
-
return str
|
|
49
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
50
|
-
.replace(/[\s_]+/g, '-')
|
|
51
|
-
.toLowerCase();
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Function to convert kebab-case to PascalCase
|
|
55
|
-
const toPascalCase = (str) => {
|
|
56
|
-
return str
|
|
57
|
-
.split('-')
|
|
58
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
59
|
-
.join('');
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// Detect dependencies in component content
|
|
63
|
-
const detectDependencies = (content) => {
|
|
64
|
-
const dependencies = {
|
|
65
|
-
needsNormalizeSize: false,
|
|
66
|
-
needsTheme: false,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
if (content.includes('@/helper/normalizeSize') || content.includes('normalizeSize')) {
|
|
70
|
-
dependencies.needsNormalizeSize = true;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (content.includes('@/constants/theme') || content.includes('from "@/constants/theme"')) {
|
|
74
|
-
dependencies.needsTheme = true;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return dependencies;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// Add helper file
|
|
81
|
-
async function addHelper(helperName, options = {}) {
|
|
82
|
-
const kebabName = toKebabCase(helperName);
|
|
83
|
-
const templatePath = path.join(templatesDir, 'helpers', `${kebabName}.ts`);
|
|
84
|
-
const helpersDir = path.join(projectRoot, 'helpers');
|
|
85
|
-
const targetPath = path.join(helpersDir, `${kebabName}.ts`);
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
if (!fs.existsSync(templatePath)) {
|
|
89
|
-
console.error(chalk.red(`✖ Helper "${helperName}" not found.`));
|
|
90
|
-
|
|
91
|
-
// List available helpers
|
|
92
|
-
const helpersTemplateDir = path.join(templatesDir, 'helpers');
|
|
93
|
-
if (fs.existsSync(helpersTemplateDir)) {
|
|
94
|
-
const helpers = fs.readdirSync(helpersTemplateDir)
|
|
95
|
-
.filter(file => file.endsWith('.ts') || file.endsWith('.tsx'))
|
|
96
|
-
.map(file => path.basename(file, path.extname(file)));
|
|
97
|
-
|
|
98
|
-
if (helpers.length > 0) {
|
|
99
|
-
console.log(chalk.yellow('\nAvailable helpers:'));
|
|
100
|
-
helpers.forEach(helper => {
|
|
101
|
-
console.log(chalk.gray(` - ${helper}`));
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
await fs.ensureDir(helpersDir);
|
|
110
|
-
|
|
111
|
-
const overwrite = process.argv.includes('--overwrite') || options.overwrite;
|
|
112
|
-
|
|
113
|
-
if (fs.existsSync(targetPath) && !overwrite) {
|
|
114
|
-
console.error(chalk.red(`✖ Helper "${helperName}" already exists at ${targetPath}`));
|
|
115
|
-
console.log(chalk.yellow(' Use --overwrite to replace it.'));
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
let content = await fs.readFile(templatePath, 'utf-8');
|
|
120
|
-
await fs.writeFile(targetPath, content, 'utf-8');
|
|
121
|
-
|
|
122
|
-
if (!options.silent) {
|
|
123
|
-
console.log(chalk.green(`✓ Added ${helperName} helper to ${path.relative(projectRoot, targetPath)}`));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return true;
|
|
127
|
-
} catch (error) {
|
|
128
|
-
console.error(chalk.red(`✖ Error adding helper: ${error.message}`));
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Add constants file
|
|
134
|
-
async function addConstant(constantName, options = {}) {
|
|
135
|
-
const kebabName = toKebabCase(constantName);
|
|
136
|
-
const templatePath = path.join(templatesDir, 'constants', `${kebabName}.ts`);
|
|
137
|
-
const constantsDir = path.join(projectRoot, 'constants');
|
|
138
|
-
const targetPath = path.join(constantsDir, `${kebabName}.ts`);
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
if (!fs.existsSync(templatePath)) {
|
|
142
|
-
console.error(chalk.red(`✖ Constant "${constantName}" not found.`));
|
|
143
|
-
|
|
144
|
-
// List available constants
|
|
145
|
-
const constantsTemplateDir = path.join(templatesDir, 'constants');
|
|
146
|
-
if (fs.existsSync(constantsTemplateDir)) {
|
|
147
|
-
const constants = fs.readdirSync(constantsTemplateDir)
|
|
148
|
-
.filter(file => file.endsWith('.ts') || file.endsWith('.tsx'))
|
|
149
|
-
.map(file => path.basename(file, path.extname(file)));
|
|
150
|
-
|
|
151
|
-
if (constants.length > 0) {
|
|
152
|
-
console.log(chalk.yellow('\nAvailable constants:'));
|
|
153
|
-
constants.forEach(constant => {
|
|
154
|
-
console.log(chalk.gray(` - ${constant}`));
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
await fs.ensureDir(constantsDir);
|
|
163
|
-
|
|
164
|
-
const overwrite = process.argv.includes('--overwrite') || options.overwrite;
|
|
165
|
-
|
|
166
|
-
if (fs.existsSync(targetPath) && !overwrite) {
|
|
167
|
-
console.error(chalk.red(`✖ Constant "${constantName}" already exists at ${targetPath}`));
|
|
168
|
-
console.log(chalk.yellow(' Use --overwrite to replace it.'));
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
let content = await fs.readFile(templatePath, 'utf-8');
|
|
173
|
-
|
|
174
|
-
// Auto-add normalizeSize dependency for theme.ts
|
|
175
|
-
if (kebabName === 'theme') {
|
|
176
|
-
const normalizeSizePath = path.join(projectRoot, 'helpers', 'normalizeSize.ts');
|
|
177
|
-
if (!fs.existsSync(normalizeSizePath)) {
|
|
178
|
-
if (!options.silent) {
|
|
179
|
-
console.log(chalk.blue('ℹ Adding normalizeSize helper (required dependency for theme)...'));
|
|
180
|
-
}
|
|
181
|
-
await addHelper('normalizeSize', { silent: true, overwrite: false });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Fix import path to use relative path
|
|
185
|
-
const normalizeSizeExists = fs.existsSync(normalizeSizePath);
|
|
186
|
-
if (normalizeSizeExists) {
|
|
187
|
-
// The import in theme.ts already uses '../helpers/normalizeSize' which is correct
|
|
188
|
-
// No need to change it
|
|
189
|
-
}
|
|
190
|
-
}
|
|
3
|
+
/**
|
|
4
|
+
* Expo App UI CLI - Production Ready
|
|
5
|
+
*
|
|
6
|
+
* A UI component library CLI for Expo React Native
|
|
7
|
+
* Copy components directly into your project and customize them
|
|
8
|
+
*/
|
|
191
9
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Function to add a component
|
|
206
|
-
async function addComponent(componentName) {
|
|
207
|
-
const kebabName = toKebabCase(componentName);
|
|
208
|
-
const templatePath = path.join(templatesDir, `${kebabName}.tsx`);
|
|
209
|
-
const componentsDir = path.join(projectRoot, 'components', 'ui');
|
|
210
|
-
const targetPath = path.join(componentsDir, `${toPascalCase(kebabName)}.tsx`);
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
// Check if template exists
|
|
214
|
-
if (!fs.existsSync(templatePath)) {
|
|
215
|
-
console.error(chalk.red(`✖ Component "${componentName}" not found.`));
|
|
216
|
-
console.log(chalk.yellow(`Available components:`));
|
|
217
|
-
|
|
218
|
-
// List available components
|
|
219
|
-
const templates = fs.readdirSync(templatesDir)
|
|
220
|
-
.filter(file => (file.endsWith('.tsx') || file.endsWith('.ts')) &&
|
|
221
|
-
!fs.statSync(path.join(templatesDir, file)).isDirectory())
|
|
222
|
-
.map(file => path.basename(file, path.extname(file)));
|
|
223
|
-
|
|
224
|
-
if (templates.length === 0) {
|
|
225
|
-
console.log(chalk.gray(' No components available.'));
|
|
226
|
-
} else {
|
|
227
|
-
templates.forEach(template => {
|
|
228
|
-
console.log(chalk.gray(` - ${template}`));
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
process.exit(1);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Ensure components/ui directory exists
|
|
235
|
-
await fs.ensureDir(componentsDir);
|
|
236
|
-
|
|
237
|
-
// Get overwrite option from command
|
|
238
|
-
const overwrite = process.argv.includes('--overwrite');
|
|
239
|
-
|
|
240
|
-
// Check if component already exists
|
|
241
|
-
if (fs.existsSync(targetPath) && !overwrite) {
|
|
242
|
-
console.error(chalk.red(`✖ Component "${componentName}" already exists at ${targetPath}`));
|
|
243
|
-
console.log(chalk.yellow(' Use --overwrite to replace it.'));
|
|
244
|
-
process.exit(1);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Read template content
|
|
248
|
-
let content = await fs.readFile(templatePath, 'utf-8');
|
|
249
|
-
|
|
250
|
-
// Detect dependencies
|
|
251
|
-
const dependencies = detectDependencies(content);
|
|
252
|
-
|
|
253
|
-
// Auto-add dependencies if needed
|
|
254
|
-
const dependenciesToAdd = [];
|
|
255
|
-
|
|
256
|
-
if (dependencies.needsNormalizeSize) {
|
|
257
|
-
const normalizeSizePath = path.join(projectRoot, 'helpers', 'normalizeSize.ts');
|
|
258
|
-
if (!fs.existsSync(normalizeSizePath)) {
|
|
259
|
-
dependenciesToAdd.push('normalizeSize helper');
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (dependencies.needsTheme) {
|
|
264
|
-
const themePath = path.join(projectRoot, 'constants', 'theme.ts');
|
|
265
|
-
if (!fs.existsSync(themePath)) {
|
|
266
|
-
dependenciesToAdd.push('theme constants');
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Show summary of what will be added
|
|
271
|
-
if (dependenciesToAdd.length > 0) {
|
|
272
|
-
console.log(chalk.blue(`\nℹ Detected dependencies: ${dependenciesToAdd.join(', ')}`));
|
|
273
|
-
console.log(chalk.gray(' Adding required dependencies...\n'));
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Add dependencies
|
|
277
|
-
if (dependencies.needsNormalizeSize) {
|
|
278
|
-
const normalizeSizePath = path.join(projectRoot, 'helpers', 'normalizeSize.ts');
|
|
279
|
-
if (!fs.existsSync(normalizeSizePath)) {
|
|
280
|
-
await addHelper('normalizeSize', { silent: true });
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (dependencies.needsTheme) {
|
|
285
|
-
const themePath = path.join(projectRoot, 'constants', 'theme.ts');
|
|
286
|
-
if (!fs.existsSync(themePath)) {
|
|
287
|
-
await addConstant('theme', { silent: true });
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Fix relative imports (e.g., CustomText from "./CustomText")
|
|
292
|
-
// This will be handled by the user's path aliases, so we keep @/ imports
|
|
293
|
-
|
|
294
|
-
// Write to target location
|
|
295
|
-
await fs.writeFile(targetPath, content, 'utf-8');
|
|
296
|
-
|
|
297
|
-
console.log(chalk.green(`✓ Added ${componentName} component to ${path.relative(projectRoot, targetPath)}`));
|
|
298
|
-
|
|
299
|
-
if (dependencies.needsNormalizeSize || dependencies.needsTheme) {
|
|
300
|
-
console.log(chalk.gray('\n💡 Tip: Make sure your project has path aliases configured for @/ imports.'));
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error(chalk.red(`✖ Error adding component: ${error.message}`));
|
|
305
|
-
process.exit(1);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Function to list available items
|
|
310
|
-
function listItems() {
|
|
311
|
-
try {
|
|
312
|
-
const items = {
|
|
313
|
-
components: [],
|
|
314
|
-
helpers: [],
|
|
315
|
-
constants: [],
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
// List components
|
|
319
|
-
if (fs.existsSync(templatesDir)) {
|
|
320
|
-
const files = fs.readdirSync(templatesDir);
|
|
321
|
-
files.forEach(file => {
|
|
322
|
-
const filePath = path.join(templatesDir, file);
|
|
323
|
-
if (fs.statSync(filePath).isFile() && (file.endsWith('.tsx') || file.endsWith('.ts'))) {
|
|
324
|
-
items.components.push(path.basename(file, path.extname(file)));
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// List helpers
|
|
330
|
-
const helpersDir = path.join(templatesDir, 'helpers');
|
|
331
|
-
if (fs.existsSync(helpersDir)) {
|
|
332
|
-
const files = fs.readdirSync(helpersDir);
|
|
333
|
-
files.forEach(file => {
|
|
334
|
-
if (file.endsWith('.ts') || file.endsWith('.tsx')) {
|
|
335
|
-
items.helpers.push(path.basename(file, path.extname(file)));
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// List constants
|
|
341
|
-
const constantsDir = path.join(templatesDir, 'constants');
|
|
342
|
-
if (fs.existsSync(constantsDir)) {
|
|
343
|
-
const files = fs.readdirSync(constantsDir);
|
|
344
|
-
files.forEach(file => {
|
|
345
|
-
if (file.endsWith('.ts') || file.endsWith('.tsx')) {
|
|
346
|
-
items.constants.push(path.basename(file, path.extname(file)));
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (items.components.length === 0 && items.helpers.length === 0 && items.constants.length === 0) {
|
|
352
|
-
console.log(chalk.yellow('No items available.'));
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (items.components.length > 0) {
|
|
357
|
-
console.log(chalk.blue('Available components:\n'));
|
|
358
|
-
items.components.forEach(item => {
|
|
359
|
-
console.log(chalk.gray(` - ${item}`));
|
|
360
|
-
});
|
|
361
|
-
console.log();
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (items.helpers.length > 0) {
|
|
365
|
-
console.log(chalk.blue('Available helpers:\n'));
|
|
366
|
-
items.helpers.forEach(item => {
|
|
367
|
-
console.log(chalk.gray(` - ${item}`));
|
|
368
|
-
});
|
|
369
|
-
console.log();
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (items.constants.length > 0) {
|
|
373
|
-
console.log(chalk.blue('Available constants:\n'));
|
|
374
|
-
items.constants.forEach(item => {
|
|
375
|
-
console.log(chalk.gray(` - ${item}`));
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
} catch (error) {
|
|
379
|
-
console.error(chalk.red(`✖ Error listing items: ${error.message}`));
|
|
380
|
-
process.exit(1);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
10
|
+
const { program } = require('commander');
|
|
11
|
+
const packageJson = require('../package.json');
|
|
12
|
+
const { handleAdd } = require('../src/commands/add');
|
|
13
|
+
const { handleList } = require('../src/commands/list');
|
|
14
|
+
const { CLIError, TemplateNotFoundError, FileExistsError, InvalidInputError } = require('../src/utils/errors');
|
|
15
|
+
const Logger = require('../src/utils/logger');
|
|
16
|
+
|
|
17
|
+
// Initialize logger
|
|
18
|
+
const logger = new Logger({
|
|
19
|
+
verbose: process.argv.includes('--verbose') || process.argv.includes('-v'),
|
|
20
|
+
silent: process.argv.includes('--silent') || process.argv.includes('-s'),
|
|
21
|
+
});
|
|
383
22
|
|
|
384
23
|
// CLI setup
|
|
385
24
|
program
|
|
386
|
-
.name('expo-ui')
|
|
25
|
+
.name('expo-app-ui')
|
|
387
26
|
.description('A UI component library for Expo React Native')
|
|
388
|
-
.version(
|
|
27
|
+
.version(packageJson.version)
|
|
28
|
+
.option('-v, --verbose', 'verbose output')
|
|
29
|
+
.option('-s, --silent', 'silent mode');
|
|
389
30
|
|
|
31
|
+
// Add command
|
|
390
32
|
program
|
|
391
33
|
.option('--overwrite', 'Overwrite existing files')
|
|
392
34
|
.command('add <name>')
|
|
393
35
|
.description('Add a component, helper, or constant to your project')
|
|
394
36
|
.action(async (name) => {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
37
|
+
try {
|
|
38
|
+
await handleAdd(name, {
|
|
39
|
+
overwrite: program.opts().overwrite || false,
|
|
40
|
+
verbose: logger.verbose,
|
|
41
|
+
silent: logger.silent,
|
|
42
|
+
});
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error instanceof TemplateNotFoundError) {
|
|
45
|
+
logger.error(`"${name}" not found.`);
|
|
46
|
+
logger.info('Run "npx expo-app-ui list" to see available items.');
|
|
47
|
+
} else if (error instanceof FileExistsError) {
|
|
48
|
+
// User was prompted and chose not to overwrite, or non-interactive mode
|
|
49
|
+
logger.warning(`File already exists: ${error.filePath}`);
|
|
50
|
+
logger.info('Use --overwrite to replace it, or run the command again and choose "y" when prompted.');
|
|
51
|
+
} else if (error instanceof InvalidInputError) {
|
|
52
|
+
logger.error(error.message);
|
|
53
|
+
} else if (error instanceof CLIError) {
|
|
54
|
+
logger.error(error.message);
|
|
55
|
+
} else {
|
|
56
|
+
logger.error(`Unexpected error: ${error.message}`);
|
|
57
|
+
if (logger.verbose) {
|
|
58
|
+
console.error(error.stack);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
process.exit(1);
|
|
417
62
|
}
|
|
418
|
-
|
|
419
|
-
// Not found
|
|
420
|
-
console.error(chalk.red(`✖ "${name}" not found.`));
|
|
421
|
-
console.log(chalk.yellow('Run "npx expo-app-ui list" to see available items.'));
|
|
422
|
-
process.exit(1);
|
|
423
63
|
});
|
|
424
64
|
|
|
65
|
+
// List command
|
|
425
66
|
program
|
|
426
67
|
.command('list')
|
|
427
68
|
.description('List all available components, helpers, and constants')
|
|
428
|
-
.action(
|
|
69
|
+
.action(() => {
|
|
70
|
+
try {
|
|
71
|
+
handleList({
|
|
72
|
+
verbose: logger.verbose,
|
|
73
|
+
silent: logger.silent,
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logger.error(`Error: ${error.message}`);
|
|
77
|
+
if (logger.verbose) {
|
|
78
|
+
console.error(error);
|
|
79
|
+
}
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Global error handler
|
|
85
|
+
process.on('unhandledRejection', (error) => {
|
|
86
|
+
logger.error(`Unhandled error: ${error.message}`);
|
|
87
|
+
if (logger.verbose) {
|
|
88
|
+
console.error(error);
|
|
89
|
+
}
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
429
92
|
|
|
93
|
+
// Parse arguments
|
|
430
94
|
program.parse();
|