rnexpopack 1.0.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.
Files changed (2) hide show
  1. package/bin/index.js +420 -0
  2. package/package.json +22 -0
package/bin/index.js ADDED
@@ -0,0 +1,420 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const chalk = require("chalk");
6
+ const ora = require("ora");
7
+ const execa = require("execa");
8
+ const prompts = require("prompts");
9
+ const fs = require("fs-extra");
10
+ const path = require("path");
11
+
12
+ // ─── Banner ───────────────────────────────────────────────────────────────────
13
+ function showBanner() {
14
+ console.log("");
15
+ console.log(chalk.cyan(" ╔══════════════════════════════════════════╗"));
16
+ console.log(chalk.cyan(" ║") + chalk.bold.white(" rnexpopack — React Native Starter CLI ") + chalk.cyan("║"));
17
+ console.log(chalk.cyan(" ║") + chalk.gray(" NativeWind · Navigation · Zustand · Axios ") + chalk.cyan("║"));
18
+ console.log(chalk.cyan(" ╚══════════════════════════════════════════╝"));
19
+ console.log("");
20
+ }
21
+
22
+ // ─── Package Lists ────────────────────────────────────────────────────────────
23
+ const EXPO_PKGS = [
24
+ "@react-navigation/native",
25
+ "@react-navigation/native-stack",
26
+ "@react-navigation/bottom-tabs",
27
+ "react-native-screens",
28
+ "react-native-safe-area-context",
29
+ "react-native-gesture-handler",
30
+ "react-native-reanimated",
31
+ "@react-native-async-storage/async-storage",
32
+ "expo-splash-screen",
33
+ ];
34
+
35
+ const NPM_PKGS = [
36
+ "nativewind",
37
+ "tailwindcss",
38
+ "zustand",
39
+ "axios",
40
+ ];
41
+
42
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
43
+ function isExpoProject() {
44
+ try {
45
+ const pkgPath = path.join(process.cwd(), "package.json");
46
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
47
+ return !!(pkg.dependencies && pkg.dependencies["expo"]);
48
+ } catch (e) {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ function detectPM() {
54
+ const cwd = process.cwd();
55
+ if (fs.existsSync(path.join(cwd, "yarn.lock"))) return "yarn";
56
+ if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
57
+ return "npm";
58
+ }
59
+
60
+ async function run(cmd, args) {
61
+ await execa(cmd, args, { stdio: "inherit", cwd: process.cwd() });
62
+ }
63
+
64
+ // ─── Config Writers ───────────────────────────────────────────────────────────
65
+ async function writeBabel() {
66
+ const content =
67
+ `module.exports = function(api) {
68
+ api.cache(true);
69
+ return {
70
+ presets: [
71
+ ["babel-preset-expo", { jsxImportSource: "nativewind" }]
72
+ ],
73
+ plugins: [
74
+ "nativewind/babel",
75
+ "react-native-reanimated/plugin"
76
+ ]
77
+ };
78
+ };
79
+ `;
80
+ fs.writeFileSync(path.join(process.cwd(), "babel.config.js"), content, "utf8");
81
+ }
82
+
83
+ async function writeTailwind() {
84
+ const content =
85
+ `/** @type {import('tailwindcss').Config} */
86
+ module.exports = {
87
+ content: [
88
+ "./App.{js,jsx,ts,tsx}",
89
+ "./src/**/*.{js,jsx,ts,tsx}"
90
+ ],
91
+ presets: [require("nativewind/preset")],
92
+ theme: {
93
+ extend: {}
94
+ },
95
+ darkMode: "class",
96
+ plugins: []
97
+ };
98
+ `;
99
+ fs.writeFileSync(path.join(process.cwd(), "tailwind.config.js"), content, "utf8");
100
+ }
101
+
102
+ // ─── Write Demo Screen ────────────────────────────────────────────────────────
103
+ async function writeShowcaseScreen() {
104
+ const screensDir = path.join(process.cwd(), "src", "screens");
105
+ const navDir = path.join(process.cwd(), "src", "navigation");
106
+ fs.ensureDirSync(screensDir);
107
+ fs.ensureDirSync(navDir);
108
+
109
+ // ShowcaseScreen.jsx
110
+ const screen =
111
+ `import React, { useState } from "react";
112
+ import {
113
+ View, Text, ScrollView, TouchableOpacity,
114
+ TextInput, useColorScheme, Alert
115
+ } from "react-native";
116
+ import { SafeAreaView } from "react-native-safe-area-context";
117
+ import AsyncStorage from "@react-native-async-storage/async-storage";
118
+ import { create } from "zustand";
119
+ import axios from "axios";
120
+
121
+ // Zustand store demo
122
+ const useStore = create((set) => ({
123
+ count: 0,
124
+ inc: () => set((s) => ({ count: s.count + 1 })),
125
+ dec: () => set((s) => ({ count: s.count - 1 })),
126
+ }));
127
+
128
+ export default function ShowcaseScreen() {
129
+ const scheme = useColorScheme();
130
+ const isDark = scheme === "dark";
131
+ const bg = isDark ? "#0f172a" : "#f8fafc";
132
+ const card = isDark ? "#1e293b" : "#ffffff";
133
+ const text = isDark ? "#f1f5f9" : "#0f172a";
134
+ const muted = isDark ? "#94a3b8" : "#64748b";
135
+ const border = isDark ? "#334155" : "#e2e8f0";
136
+
137
+ const { count, inc, dec } = useStore();
138
+ const [input, setInput] = useState("");
139
+ const [saved, setSaved] = useState("");
140
+ const [api, setApi] = useState("tap karein");
141
+ const [loading, setLoad] = useState(false);
142
+
143
+ const save = async () => {
144
+ await AsyncStorage.setItem("@demo", input);
145
+ setSaved(input); setInput("");
146
+ };
147
+ const load = async () => {
148
+ const v = await AsyncStorage.getItem("@demo");
149
+ setSaved(v || "(kuch nahi mila)");
150
+ };
151
+ const callApi = async () => {
152
+ setLoad(true); setApi("loading...");
153
+ try {
154
+ const { data } = await axios.get("https://jsonplaceholder.typicode.com/todos/1");
155
+ setApi("ID:" + data.id + " — " + data.title.slice(0, 30) + "...");
156
+ } catch { setApi("network error"); }
157
+ setLoad(false);
158
+ };
159
+
160
+ const Card = ({ title, color, children }) => (
161
+ <View style={{
162
+ backgroundColor: card, borderRadius: 14, padding: 16,
163
+ marginBottom: 12, borderWidth: 1, borderColor: border
164
+ }}>
165
+ <View style={{ flexDirection:"row", alignItems:"center", marginBottom: 10 }}>
166
+ <View style={{
167
+ width: 10, height: 10, borderRadius: 5,
168
+ backgroundColor: color, marginRight: 8
169
+ }}/>
170
+ <Text style={{ color: text, fontWeight: "700", fontSize: 13, fontFamily: "monospace" }}>
171
+ {title}
172
+ </Text>
173
+ </View>
174
+ {children}
175
+ </View>
176
+ );
177
+
178
+ return (
179
+ <SafeAreaView style={{ flex: 1, backgroundColor: bg }}>
180
+ <ScrollView contentContainerStyle={{ padding: 16, paddingBottom: 48 }}>
181
+
182
+ <Text style={{ fontSize: 22, fontWeight: "800", color: text, marginBottom: 4 }}>
183
+ rnexpopack 📦
184
+ </Text>
185
+ <Text style={{ fontSize: 13, color: muted, marginBottom: 20 }}>
186
+ Tamam installed packages ka live demo
187
+ </Text>
188
+
189
+ {/* NativeWind */}
190
+ <Card title="nativewind — Tailwind CSS" color="#0d9488">
191
+ <View className="flex-row flex-wrap gap-2">
192
+ {["bg-blue-500","bg-rose-500","bg-amber-500","bg-emerald-500","bg-purple-500"].map(c => (
193
+ <View key={c} className={\`\${c} px-3 py-1.5 rounded-lg\`}>
194
+ <Text className="text-white text-xs font-bold">{c}</Text>
195
+ </View>
196
+ ))}
197
+ </View>
198
+ </Card>
199
+
200
+ {/* AsyncStorage */}
201
+ <Card title="@react-native-async-storage" color="#2563eb">
202
+ <TextInput
203
+ value={input} onChangeText={setInput}
204
+ placeholder="Kuch likho..." placeholderTextColor={muted}
205
+ style={{
206
+ borderWidth:1, borderColor:border, borderRadius:8,
207
+ paddingHorizontal:12, paddingVertical:8, color:text,
208
+ fontSize:14, marginBottom:8
209
+ }}
210
+ />
211
+ <View style={{ flexDirection:"row", gap:8, marginBottom:8 }}>
212
+ <TouchableOpacity onPress={save}
213
+ style={{ backgroundColor:"#2563eb", borderRadius:8, paddingHorizontal:14, paddingVertical:8 }}>
214
+ <Text style={{ color:"#fff", fontWeight:"600", fontSize:13 }}>Save</Text>
215
+ </TouchableOpacity>
216
+ <TouchableOpacity onPress={load}
217
+ style={{ borderWidth:1, borderColor:"#2563eb", borderRadius:8, paddingHorizontal:14, paddingVertical:8 }}>
218
+ <Text style={{ color:"#2563eb", fontWeight:"600", fontSize:13 }}>Load</Text>
219
+ </TouchableOpacity>
220
+ </View>
221
+ {!!saved && <Text style={{ color:"#2563eb", fontSize:13 }}>Saved: "{saved}"</Text>}
222
+ </Card>
223
+
224
+ {/* Zustand */}
225
+ <Card title="zustand — Global State" color="#d97706">
226
+ <View style={{ alignItems:"center" }}>
227
+ <Text style={{ fontSize: 52, fontWeight:"800", color:text }}>{count}</Text>
228
+ <View style={{ flexDirection:"row", gap:12, marginTop:10 }}>
229
+ {[["−", dec,"#ef4444"], ["+", inc,"#22c55e"]].map(([label, fn, bg]) => (
230
+ <TouchableOpacity key={label} onPress={fn}
231
+ style={{ backgroundColor:bg, borderRadius:10, paddingHorizontal:28, paddingVertical:10 }}>
232
+ <Text style={{ color:"#fff", fontSize:20, fontWeight:"800" }}>{label}</Text>
233
+ </TouchableOpacity>
234
+ ))}
235
+ </View>
236
+ </View>
237
+ </Card>
238
+
239
+ {/* Axios */}
240
+ <Card title="axios — HTTP Client" color="#7c3aed">
241
+ <TouchableOpacity onPress={callApi} disabled={loading}
242
+ style={{
243
+ backgroundColor: loading ? "#94a3b8" : "#7c3aed",
244
+ borderRadius:8, paddingHorizontal:14, paddingVertical:10,
245
+ alignSelf:"flex-start", marginBottom:10
246
+ }}>
247
+ <Text style={{ color:"#fff", fontWeight:"600", fontSize:13 }}>
248
+ {loading ? "Loading..." : "API Call Karein"}
249
+ </Text>
250
+ </TouchableOpacity>
251
+ <Text style={{ color:"#7c3aed", fontSize:13 }}>{api}</Text>
252
+ </Card>
253
+
254
+ {/* Dark Mode */}
255
+ <Card title="Dark Mode" color="#475569">
256
+ <Text style={{ color:muted, fontSize:13 }}>
257
+ Current:{" "}
258
+ <Text style={{ color:text, fontWeight:"700" }}>
259
+ {isDark ? "🌙 Dark" : "☀️ Light"}
260
+ </Text>
261
+ {"\\n"}useColorScheme() + NativeWind darkMode:"class"
262
+ </Text>
263
+ </Card>
264
+
265
+ <Text style={{ color:muted, fontSize:11, textAlign:"center", marginTop:8 }}>
266
+ Yeh screen delete kar sakte hain — src/screens/ mein apna kaam shuru karein
267
+ </Text>
268
+ </ScrollView>
269
+ </SafeAreaView>
270
+ );
271
+ }
272
+ `;
273
+
274
+ // Navigator
275
+ const nav =
276
+ `import React from "react";
277
+ import { NavigationContainer } from "@react-navigation/native";
278
+ import { createNativeStackNavigator } from "@react-navigation/native-stack";
279
+ import ShowcaseScreen from "../screens/ShowcaseScreen";
280
+
281
+ const Stack = createNativeStackNavigator();
282
+
283
+ export default function Navigator() {
284
+ return (
285
+ <NavigationContainer>
286
+ <Stack.Navigator screenOptions={{ headerShown: false }}>
287
+ <Stack.Screen name="Showcase" component={ShowcaseScreen} />
288
+ {/* apni screens yahan add karein */}
289
+ </Stack.Navigator>
290
+ </NavigationContainer>
291
+ );
292
+ }
293
+ `;
294
+
295
+ fs.writeFileSync(path.join(screensDir, "ShowcaseScreen.jsx"), screen, "utf8");
296
+ fs.writeFileSync(path.join(navDir, "Navigator.jsx"), nav, "utf8");
297
+ }
298
+
299
+ // ─── Write App.jsx ────────────────────────────────────────────────────────────
300
+ async function writeAppJsx() {
301
+ const appPath = path.join(process.cwd(), "App.jsx");
302
+ if (fs.existsSync(appPath)) return false;
303
+
304
+ const content =
305
+ `import React, { useEffect } from "react";
306
+ import * as SplashScreen from "expo-splash-screen";
307
+ import { GestureHandlerRootView } from "react-native-gesture-handler";
308
+ import Navigator from "./src/navigation/Navigator";
309
+
310
+ SplashScreen.preventAutoHideAsync();
311
+
312
+ export default function App() {
313
+ useEffect(() => { SplashScreen.hideAsync(); }, []);
314
+ return (
315
+ <GestureHandlerRootView style={{ flex: 1 }}>
316
+ <Navigator />
317
+ </GestureHandlerRootView>
318
+ );
319
+ }
320
+ `;
321
+ fs.writeFileSync(appPath, content, "utf8");
322
+ return true;
323
+ }
324
+
325
+ // ─── Main ─────────────────────────────────────────────────────────────────────
326
+ async function main() {
327
+ showBanner();
328
+
329
+ // Guard: must be inside expo project
330
+ if (!isExpoProject()) {
331
+ console.log(chalk.red(" ✖ Expo project nahi mila!"));
332
+ console.log(chalk.gray(" Pehle chalayen:"));
333
+ console.log(chalk.cyan(" npx create-expo-app my-app && cd my-app\n"));
334
+ process.exit(1);
335
+ }
336
+
337
+ const pm = detectPM();
338
+ console.log(chalk.gray(" Package manager: ") + chalk.white(pm));
339
+ console.log("");
340
+
341
+ // Optional packages prompt
342
+ const res = await prompts([
343
+ {
344
+ type: "multiselect",
345
+ name: "opts",
346
+ message: "Optional packages select karein:",
347
+ choices: [
348
+ { title: "@tanstack/react-query (smart caching)", value: "query", selected: true },
349
+ { title: "react-native-bootsplash (animated splash)", value: "bootsplash", selected: false },
350
+ ],
351
+ hint: "Space=toggle Enter=confirm",
352
+ },
353
+ {
354
+ type: "confirm",
355
+ name: "demo",
356
+ message: "ShowcaseScreen demo add karein?",
357
+ initial: true,
358
+ },
359
+ ]);
360
+
361
+ const optPkgs = [];
362
+ if (res.opts && res.opts.includes("query")) optPkgs.push("@tanstack/react-query");
363
+ if (res.opts && res.opts.includes("bootsplash")) optPkgs.push("react-native-bootsplash");
364
+
365
+ console.log("");
366
+
367
+ // Step 1 — expo install
368
+ const s1 = ora({ text: chalk.cyan("Expo packages install ho rahi hain..."), color: "cyan" }).start();
369
+ try {
370
+ await run("npx", ["expo", "install", ...EXPO_PKGS]);
371
+ s1.succeed(chalk.green("Expo packages ✔"));
372
+ } catch (e) { s1.fail(chalk.red("Expo packages fail")); throw e; }
373
+
374
+ // Step 2 — npm install
375
+ const allNpm = [...NPM_PKGS, ...optPkgs];
376
+ const s2 = ora({ text: chalk.cyan("npm packages install ho rahi hain..."), color: "cyan" }).start();
377
+ try {
378
+ const cmd = pm === "yarn" ? "yarn" : pm === "pnpm" ? "pnpm" : "npm";
379
+ const args = pm === "yarn" ? ["add", ...allNpm] : ["install", ...allNpm];
380
+ await run(cmd, args);
381
+ s2.succeed(chalk.green("npm packages ✔"));
382
+ } catch (e) { s2.fail(chalk.red("npm packages fail")); throw e; }
383
+
384
+ // Step 3 — configs
385
+ const s3 = ora({ text: chalk.cyan("Config files likh raha hoon..."), color: "cyan" }).start();
386
+ try {
387
+ await writeBabel();
388
+ await writeTailwind();
389
+ s3.succeed(chalk.green("babel.config.js + tailwind.config.js ✔"));
390
+ } catch (e) { s3.fail(chalk.red("Config fail")); throw e; }
391
+
392
+ // Step 4 — demo screen
393
+ if (res.demo) {
394
+ const s4 = ora({ text: chalk.cyan("Demo screen bana raha hoon..."), color: "cyan" }).start();
395
+ try {
396
+ await writeShowcaseScreen();
397
+ const appCreated = await writeAppJsx();
398
+ s4.succeed(chalk.green("src/screens/ShowcaseScreen.jsx ✔" + (appCreated ? " App.jsx ✔" : "")));
399
+ } catch (e) { s4.fail(chalk.red("Demo screen fail")); throw e; }
400
+ }
401
+
402
+ // Done!
403
+ console.log("");
404
+ console.log(chalk.bold.green(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
405
+ console.log(chalk.bold.green(" ✅ Sab kuch ready hai!"));
406
+ console.log(chalk.bold.green(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
407
+ console.log("");
408
+ console.log(" " + chalk.gray("App chalao:") + " " + chalk.cyan("npx expo start"));
409
+ console.log("");
410
+ console.log(" " + chalk.gray("Installed:"));
411
+ [...EXPO_PKGS, ...allNpm].forEach((p) =>
412
+ console.log(" " + chalk.gray(" ▸ ") + chalk.white(p))
413
+ );
414
+ console.log("");
415
+ }
416
+
417
+ main().catch((err) => {
418
+ console.error("\n" + chalk.red(" ✖ Error: ") + err.message);
419
+ process.exit(1);
420
+ });
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "rnexpopack",
3
+ "version": "1.0.0",
4
+ "description": "A CLI tool to setup React Native Expo project with NativeWind, Navigation, Zustand and Axios",
5
+ "keywords": ["react-native", "expo", "nativewind", "navigation", "starter"],
6
+ "author": "Abdul Haseeb",
7
+ "license": "MIT",
8
+ "preferGlobal": true,
9
+ "bin": {
10
+ "rnexpopack": "./bin/index.js"
11
+ },
12
+ "dependencies": {
13
+ "chalk": "4.1.2",
14
+ "ora": "5.4.1",
15
+ "execa": "5.1.1",
16
+ "prompts": "2.4.2",
17
+ "fs-extra": "11.2.0"
18
+ },
19
+ "engines": {
20
+ "node": ">=16.0.0"
21
+ }
22
+ }