twinbloc-rn-starter 0.1.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/bin/cli.js +113 -0
- package/package.json +40 -0
- package/template/react-native-starter/.vscode/extensions.json +1 -0
- package/template/react-native-starter/.vscode/settings.json +7 -0
- package/template/react-native-starter/README.md +50 -0
- package/template/react-native-starter/app/_layout.tsx +5 -0
- package/template/react-native-starter/app/index.tsx +86 -0
- package/template/react-native-starter/app.json +50 -0
- package/template/react-native-starter/assets/images/android-icon-background.png +0 -0
- package/template/react-native-starter/assets/images/android-icon-foreground.png +0 -0
- package/template/react-native-starter/assets/images/android-icon-monochrome.png +0 -0
- package/template/react-native-starter/assets/images/favicon.png +0 -0
- package/template/react-native-starter/assets/images/icon.png +0 -0
- package/template/react-native-starter/assets/images/partial-react-logo.png +0 -0
- package/template/react-native-starter/assets/images/react-logo.png +0 -0
- package/template/react-native-starter/assets/images/react-logo@2x.png +0 -0
- package/template/react-native-starter/assets/images/react-logo@3x.png +0 -0
- package/template/react-native-starter/assets/images/splash-icon.png +0 -0
- package/template/react-native-starter/eslint.config.js +10 -0
- package/template/react-native-starter/package-lock.json +12858 -0
- package/template/react-native-starter/package.json +48 -0
- package/template/react-native-starter/src/state/useCounterStore.ts +15 -0
- package/template/react-native-starter/tsconfig.json +17 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import { spawnSync } from "child_process";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
12
|
+
const templateRoot = path.join(packageRoot, "template", "react-native-starter");
|
|
13
|
+
const examplesRoot = path.join(packageRoot, "examples");
|
|
14
|
+
|
|
15
|
+
const program = new Command();
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name("create-twinbloc-app")
|
|
19
|
+
.argument("<project-directory>")
|
|
20
|
+
.option("--example <name>", "example variant", "base")
|
|
21
|
+
.option("--skip-install", "skip dependency installation", false)
|
|
22
|
+
.option("--pm <npm|yarn|pnpm|bun>", "force package manager")
|
|
23
|
+
.parse(process.argv);
|
|
24
|
+
|
|
25
|
+
const [projectDirectory] = program.args;
|
|
26
|
+
const options = program.opts();
|
|
27
|
+
|
|
28
|
+
if (!projectDirectory) {
|
|
29
|
+
console.error(chalk.red("Project directory is required."));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const targetDir = path.resolve(process.cwd(), projectDirectory);
|
|
34
|
+
|
|
35
|
+
const isEmptyDir = async (dir) => {
|
|
36
|
+
if (!fs.existsSync(dir)) return true;
|
|
37
|
+
const entries = await fs.readdir(dir);
|
|
38
|
+
return entries.length === 0;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const resolveExamplePath = (name) => {
|
|
42
|
+
if (!name || name === "base") return null;
|
|
43
|
+
return path.join(examplesRoot, name);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const detectPackageManager = () => {
|
|
47
|
+
if (options.pm) return options.pm;
|
|
48
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
49
|
+
if (userAgent.includes("pnpm")) return "pnpm";
|
|
50
|
+
if (userAgent.includes("yarn")) return "yarn";
|
|
51
|
+
if (userAgent.includes("bun")) return "bun";
|
|
52
|
+
return "npm";
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const cleanLockfilesForBun = async (dir) => {
|
|
56
|
+
const lockfiles = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"];
|
|
57
|
+
await Promise.all(lockfiles.map((file) => fs.remove(path.join(dir, file))));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const runInstall = (dir, pm) => {
|
|
61
|
+
const cmd = pm === "yarn" ? "yarn" : pm;
|
|
62
|
+
const args = pm === "npm" ? ["install"] : ["install"];
|
|
63
|
+
const result = spawnSync(cmd, args, { cwd: dir, stdio: "inherit" });
|
|
64
|
+
if (result.status !== 0) process.exit(result.status);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const main = async () => {
|
|
68
|
+
const empty = await isEmptyDir(targetDir);
|
|
69
|
+
if (!empty) {
|
|
70
|
+
console.error(chalk.red("Target directory must be empty."));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await fs.ensureDir(targetDir);
|
|
75
|
+
|
|
76
|
+
if (!fs.existsSync(templateRoot)) {
|
|
77
|
+
console.error(chalk.red("Template folder not found."));
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await fs.copy(templateRoot, targetDir);
|
|
82
|
+
|
|
83
|
+
const examplePath = resolveExamplePath(options.example);
|
|
84
|
+
if (examplePath) {
|
|
85
|
+
if (!fs.existsSync(examplePath)) {
|
|
86
|
+
console.error(chalk.red(`Example not found: ${options.example}`));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
await fs.copy(examplePath, targetDir, { overwrite: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const targetPackageJson = path.join(targetDir, "package.json");
|
|
93
|
+
if (fs.existsSync(targetPackageJson)) {
|
|
94
|
+
const pkg = await fs.readJson(targetPackageJson);
|
|
95
|
+
pkg.name = path.basename(targetDir);
|
|
96
|
+
await fs.writeJson(targetPackageJson, pkg, { spaces: 2 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!options.skipInstall) {
|
|
100
|
+
const pm = detectPackageManager();
|
|
101
|
+
if (pm === "bun") {
|
|
102
|
+
await cleanLockfilesForBun(targetDir);
|
|
103
|
+
}
|
|
104
|
+
runInstall(targetDir, pm);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log(chalk.green("Project created successfully."));
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
main().catch((error) => {
|
|
111
|
+
console.error(chalk.red(error.message));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "twinbloc-rn-starter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React Native starter template with optional examples",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-twinbloc-app": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"template",
|
|
12
|
+
"examples",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react-native",
|
|
18
|
+
"starter",
|
|
19
|
+
"template",
|
|
20
|
+
"cli",
|
|
21
|
+
"create-app"
|
|
22
|
+
],
|
|
23
|
+
"author": "teepheh-git",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"lint": "eslint .",
|
|
30
|
+
"test": "node ./bin/cli.js --help"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"chalk": "^5.4.1",
|
|
34
|
+
"commander": "^12.1.0",
|
|
35
|
+
"fs-extra": "^11.2.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"eslint": "^8.57.1"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "recommendations": ["expo.vscode-expo-tools"] }
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Welcome to your Expo app 👋
|
|
2
|
+
|
|
3
|
+
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
|
4
|
+
|
|
5
|
+
## Get started
|
|
6
|
+
|
|
7
|
+
1. Install dependencies
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. Start the app
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx expo start
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
In the output, you'll find options to open the app in a
|
|
20
|
+
|
|
21
|
+
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
|
22
|
+
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
|
23
|
+
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
|
24
|
+
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
|
25
|
+
|
|
26
|
+
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|
|
27
|
+
|
|
28
|
+
## Get a fresh project
|
|
29
|
+
|
|
30
|
+
When you're ready, run:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run reset-project
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
|
|
37
|
+
|
|
38
|
+
## Learn more
|
|
39
|
+
|
|
40
|
+
To learn more about developing your project with Expo, look at the following resources:
|
|
41
|
+
|
|
42
|
+
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
|
|
43
|
+
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
|
|
44
|
+
|
|
45
|
+
## Join the community
|
|
46
|
+
|
|
47
|
+
Join our community of developers creating universal apps.
|
|
48
|
+
|
|
49
|
+
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
|
|
50
|
+
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Pressable, SafeAreaView, StyleSheet, Text, View } from "react-native";
|
|
2
|
+
import { useCounterStore } from "@/src/state/useCounterStore";
|
|
3
|
+
|
|
4
|
+
export default function Index() {
|
|
5
|
+
const count = useCounterStore((state) => state.count);
|
|
6
|
+
const increment = useCounterStore((state) => state.increment);
|
|
7
|
+
const decrement = useCounterStore((state) => state.decrement);
|
|
8
|
+
const reset = useCounterStore((state) => state.reset);
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<SafeAreaView style={styles.safeArea}>
|
|
12
|
+
<View style={styles.container}>
|
|
13
|
+
<Text style={styles.title}>Twinbloc Starter</Text>
|
|
14
|
+
<Text style={styles.subtitle}>Edit app/index.tsx to customize.</Text>
|
|
15
|
+
<Text style={styles.counter}>Count: {count}</Text>
|
|
16
|
+
<View style={styles.actions}>
|
|
17
|
+
<Pressable style={styles.button} onPress={decrement}>
|
|
18
|
+
<Text style={styles.buttonText}>-</Text>
|
|
19
|
+
</Pressable>
|
|
20
|
+
<Pressable style={styles.button} onPress={increment}>
|
|
21
|
+
<Text style={styles.buttonText}>+</Text>
|
|
22
|
+
</Pressable>
|
|
23
|
+
<Pressable style={styles.secondaryButton} onPress={reset}>
|
|
24
|
+
<Text style={styles.secondaryButtonText}>Reset</Text>
|
|
25
|
+
</Pressable>
|
|
26
|
+
</View>
|
|
27
|
+
</View>
|
|
28
|
+
</SafeAreaView>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const styles = StyleSheet.create({
|
|
33
|
+
safeArea: {
|
|
34
|
+
flex: 1,
|
|
35
|
+
backgroundColor: "#0B0F1A"
|
|
36
|
+
},
|
|
37
|
+
container: {
|
|
38
|
+
flex: 1,
|
|
39
|
+
padding: 24,
|
|
40
|
+
justifyContent: "center",
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
gap: 12
|
|
43
|
+
},
|
|
44
|
+
title: {
|
|
45
|
+
color: "#F5F7FF",
|
|
46
|
+
fontSize: 28,
|
|
47
|
+
fontWeight: "700"
|
|
48
|
+
},
|
|
49
|
+
subtitle: {
|
|
50
|
+
color: "#B2B9D2",
|
|
51
|
+
textAlign: "center"
|
|
52
|
+
},
|
|
53
|
+
counter: {
|
|
54
|
+
color: "#F5F7FF",
|
|
55
|
+
fontSize: 24,
|
|
56
|
+
fontWeight: "600"
|
|
57
|
+
},
|
|
58
|
+
actions: {
|
|
59
|
+
flexDirection: "row",
|
|
60
|
+
gap: 12,
|
|
61
|
+
marginTop: 16
|
|
62
|
+
},
|
|
63
|
+
button: {
|
|
64
|
+
paddingVertical: 10,
|
|
65
|
+
paddingHorizontal: 18,
|
|
66
|
+
borderRadius: 12,
|
|
67
|
+
backgroundColor: "#4C6FFF"
|
|
68
|
+
},
|
|
69
|
+
buttonText: {
|
|
70
|
+
color: "#FFFFFF",
|
|
71
|
+
fontSize: 18,
|
|
72
|
+
fontWeight: "600"
|
|
73
|
+
},
|
|
74
|
+
secondaryButton: {
|
|
75
|
+
paddingVertical: 10,
|
|
76
|
+
paddingHorizontal: 18,
|
|
77
|
+
borderRadius: 12,
|
|
78
|
+
borderWidth: 1,
|
|
79
|
+
borderColor: "#4C6FFF"
|
|
80
|
+
},
|
|
81
|
+
secondaryButtonText: {
|
|
82
|
+
color: "#4C6FFF",
|
|
83
|
+
fontSize: 16,
|
|
84
|
+
fontWeight: "600"
|
|
85
|
+
}
|
|
86
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expo": {
|
|
3
|
+
"name": "react-native-starter",
|
|
4
|
+
"slug": "react-native-starter",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"orientation": "portrait",
|
|
7
|
+
"icon": "./assets/images/icon.png",
|
|
8
|
+
"scheme": "reactnativestarter",
|
|
9
|
+
"userInterfaceStyle": "automatic",
|
|
10
|
+
"newArchEnabled": true,
|
|
11
|
+
"ios": {
|
|
12
|
+
"supportsTablet": true,
|
|
13
|
+
"bundleIdentifier": "com.bulkyy.reactnativestarter"
|
|
14
|
+
},
|
|
15
|
+
"android": {
|
|
16
|
+
"adaptiveIcon": {
|
|
17
|
+
"backgroundColor": "#E6F4FE",
|
|
18
|
+
"foregroundImage": "./assets/images/android-icon-foreground.png",
|
|
19
|
+
"backgroundImage": "./assets/images/android-icon-background.png",
|
|
20
|
+
"monochromeImage": "./assets/images/android-icon-monochrome.png"
|
|
21
|
+
},
|
|
22
|
+
"edgeToEdgeEnabled": true,
|
|
23
|
+
"predictiveBackGestureEnabled": false,
|
|
24
|
+
"package": "com.bulkyy.reactnativestarter"
|
|
25
|
+
},
|
|
26
|
+
"web": {
|
|
27
|
+
"output": "static",
|
|
28
|
+
"favicon": "./assets/images/favicon.png"
|
|
29
|
+
},
|
|
30
|
+
"plugins": [
|
|
31
|
+
"expo-router",
|
|
32
|
+
[
|
|
33
|
+
"expo-splash-screen",
|
|
34
|
+
{
|
|
35
|
+
"image": "./assets/images/splash-icon.png",
|
|
36
|
+
"imageWidth": 200,
|
|
37
|
+
"resizeMode": "contain",
|
|
38
|
+
"backgroundColor": "#ffffff",
|
|
39
|
+
"dark": {
|
|
40
|
+
"backgroundColor": "#000000"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
],
|
|
45
|
+
"experiments": {
|
|
46
|
+
"typedRoutes": true,
|
|
47
|
+
"reactCompiler": true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|