@thunder-stack/create-thunder-app 0.0.1

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 (69) hide show
  1. package/index.js +229 -0
  2. package/package.json +26 -0
  3. package/postpublish.js +14 -0
  4. package/prepublish.js +68 -0
  5. package/template/.env.example +13 -0
  6. package/template/README.md +1 -0
  7. package/template/THUNDER_STACK.md +120 -0
  8. package/template/client/assets/logo.svg +36 -0
  9. package/template/client/expo/App.test.tsx +20 -0
  10. package/template/client/expo/App.tsx +16 -0
  11. package/template/client/expo/app.json +30 -0
  12. package/template/client/expo/babel.config.js +12 -0
  13. package/template/client/expo/global.css +3 -0
  14. package/template/client/expo/jest.config.js +7 -0
  15. package/template/client/expo/metro.config.js +21 -0
  16. package/template/client/expo/package.json +33 -0
  17. package/template/client/expo/tsconfig.json +6 -0
  18. package/template/client/next/app/globals.css +14 -0
  19. package/template/client/next/app/layout.tsx +28 -0
  20. package/template/client/next/app/page.test.tsx +13 -0
  21. package/template/client/next/app/page.tsx +10 -0
  22. package/template/client/next/next-env.d.ts +5 -0
  23. package/template/client/next/next.config.js +21 -0
  24. package/template/client/next/package.json +39 -0
  25. package/template/client/next/playwright.config.ts +17 -0
  26. package/template/client/next/postcss.config.js +6 -0
  27. package/template/client/next/tsconfig.json +30 -0
  28. package/template/client/next/vitest.config.ts +11 -0
  29. package/template/client/next/vitest.setup.ts +1 -0
  30. package/template/client/shared/index.ts +2 -0
  31. package/template/client/shared/package.json +24 -0
  32. package/template/client/shared/src/components/Button.tsx +38 -0
  33. package/template/client/shared/src/components/Card.tsx +17 -0
  34. package/template/client/shared/src/features/auth/login.tsx +139 -0
  35. package/template/client/shared/src/features/dashboard/index.tsx +232 -0
  36. package/template/client/shared/src/features/home/screen.tsx +212 -0
  37. package/template/client/shared/src/features/management/roles.tsx +356 -0
  38. package/template/client/shared/src/features/management/users.tsx +245 -0
  39. package/template/client/shared/src/nativewind-env.d.ts +1 -0
  40. package/template/client/shared/src/utils/auth.ts +21 -0
  41. package/template/client/shared/tailwind.config.js +19 -0
  42. package/template/client/shared/tsconfig.json +9 -0
  43. package/template/package.json +47 -0
  44. package/template/packages/tsconfig/base.json +20 -0
  45. package/template/packages/tsconfig/package.json +11 -0
  46. package/template/pnpm-workspace.yaml +4 -0
  47. package/template/scripts/clean.js +56 -0
  48. package/template/server/db/drizzle.config.ts +10 -0
  49. package/template/server/db/migrations/0000_loving_mindworm.sql +86 -0
  50. package/template/server/db/migrations/meta/0000_snapshot.json +549 -0
  51. package/template/server/db/migrations/meta/_journal.json +13 -0
  52. package/template/server/db/package.json +28 -0
  53. package/template/server/db/src/index.ts +16 -0
  54. package/template/server/db/src/migrate.ts +28 -0
  55. package/template/server/db/src/schema/auth.ts +73 -0
  56. package/template/server/db/src/schema/index.ts +2 -0
  57. package/template/server/db/src/schema/rbac.ts +72 -0
  58. package/template/server/db/src/schema.ts +1 -0
  59. package/template/server/db/src/seed.ts +204 -0
  60. package/template/server/db/src/services/rbac.ts +144 -0
  61. package/template/server/db/src/services/user.ts +34 -0
  62. package/template/server/db/src/types/index.ts +34 -0
  63. package/template/server/db/tsconfig.json +8 -0
  64. package/template/server/hono/package.json +22 -0
  65. package/template/server/hono/src/auth.ts +29 -0
  66. package/template/server/hono/src/index.ts +215 -0
  67. package/template/server/hono/tsconfig.json +8 -0
  68. package/template/server/hono/wrangler.toml +12 -0
  69. package/template/turbo.json +68 -0
package/index.js ADDED
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import readline from 'readline';
6
+ import { fileURLToPath } from 'url';
7
+ import { execSync } from 'child_process';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ // Interactive readline interface
13
+ const rl = readline.createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout
16
+ });
17
+
18
+ const question = (query) => new Promise((resolve) => rl.question(query, resolve));
19
+
20
+ function createLink(target, source) {
21
+ if (fs.existsSync(target)) {
22
+ try {
23
+ const stats = fs.lstatSync(target);
24
+ if (stats.isSymbolicLink()) {
25
+ fs.unlinkSync(target);
26
+ } else if (stats.isDirectory()) {
27
+ fs.rmdirSync(target);
28
+ }
29
+ } catch (err) {
30
+ // Ignore busy target folders
31
+ }
32
+ }
33
+
34
+ const type = process.platform === 'win32' ? 'junction' : 'dir';
35
+ try {
36
+ fs.symlinkSync(source, target, type);
37
+ console.log(`Mapped link: ${target} -> ${source}`);
38
+ } catch (err) {
39
+ console.error(`Error mapping link ${target}:`, err.message);
40
+ }
41
+ }
42
+
43
+ async function main() {
44
+ console.log('\x1b[33m%s\x1b[0m', '⚡ Welcome to the THUNDER Stack Installer! ⚡');
45
+
46
+ let projectName = process.argv[2]?.trim();
47
+ let targetDir;
48
+ let isCurrentDir = false;
49
+
50
+ if (projectName === '.') {
51
+ targetDir = process.cwd();
52
+ projectName = path.basename(targetDir);
53
+ isCurrentDir = true;
54
+ } else if (projectName) {
55
+ projectName = projectName.toLowerCase();
56
+ targetDir = path.resolve(process.cwd(), projectName);
57
+ } else {
58
+ projectName = await question('Enter your project name (default: my-thunder-app): ');
59
+ projectName = projectName.trim().toLowerCase() || 'my-thunder-app';
60
+ targetDir = path.resolve(process.cwd(), projectName);
61
+ }
62
+
63
+ // Sanitize name to be compliant with npm package naming guidelines
64
+ projectName = projectName.toLowerCase().replace(/[^a-z0-9-_]/g, '-');
65
+
66
+ if (isCurrentDir) {
67
+ const existingFiles = fs.readdirSync(targetDir);
68
+ const nonIgnoredFiles = existingFiles.filter(file => ![
69
+ 'node_modules',
70
+ 'dist',
71
+ '.next',
72
+ '.turbo',
73
+ '.expo',
74
+ 'create-thunder-app',
75
+ 'package.json'
76
+ ].includes(file));
77
+
78
+ if (nonIgnoredFiles.length > 0) {
79
+ const overwrite = await question('Current directory is not empty. Proceed anyway? (y/N): ');
80
+ if (overwrite.toLowerCase().trim() !== 'y') {
81
+ console.log('Aborted.');
82
+ rl.close();
83
+ process.exit(0);
84
+ }
85
+ }
86
+ } else if (fs.existsSync(targetDir)) {
87
+ const overwrite = await question(`Directory "${projectName}" already exists. Overwrite? (y/N): `);
88
+ if (overwrite.toLowerCase().trim() !== 'y') {
89
+ console.log('Aborted.');
90
+ rl.close();
91
+ process.exit(0);
92
+ }
93
+ console.log(`Cleaning target directory: ${targetDir}`);
94
+ fs.rmSync(targetDir, { recursive: true, force: true });
95
+ }
96
+
97
+ console.log(`\nCreating a new THUNDER Stack app in: ${targetDir}\n`);
98
+ if (!isCurrentDir) {
99
+ fs.mkdirSync(targetDir, { recursive: true });
100
+ }
101
+
102
+ // Locate the source template directory
103
+ // In development, the template is the monorepo root.
104
+ // We check if we are inside a monorepo development structure
105
+ let sourceDir = path.resolve(__dirname, '../..');
106
+
107
+ // Verify if it's the correct source monorepo
108
+ const rootPackageJsonPath = path.join(sourceDir, 'package.json');
109
+ let isDevMode = false;
110
+ if (fs.existsSync(rootPackageJsonPath)) {
111
+ const pkg = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf8'));
112
+ if (pkg.name === 'thunder-monorepo') {
113
+ isDevMode = true;
114
+ }
115
+ }
116
+
117
+ if (!isDevMode) {
118
+ sourceDir = path.join(__dirname, 'template');
119
+ }
120
+
121
+ const ignoreList = [
122
+ 'node_modules',
123
+ 'dist',
124
+ '.next',
125
+ '.open-next',
126
+ '.turbo',
127
+ '.expo',
128
+ 'web-build',
129
+ '.git',
130
+ 'pnpm-lock.yaml',
131
+ '.env',
132
+ 'create-thunder-app', // Ignore this package directory itself
133
+ ];
134
+
135
+ const safeProjectName = projectName.toLowerCase();
136
+ const capitalizedProjectName = projectName.charAt(0).toUpperCase() + projectName.slice(1);
137
+
138
+ function copyFolderRecursive(src, dest) {
139
+ if (!fs.existsSync(src)) return;
140
+
141
+ const stats = fs.statSync(src);
142
+ if (stats.isDirectory()) {
143
+ const folderName = path.basename(src);
144
+ if (ignoreList.includes(folderName)) return;
145
+
146
+ if (!fs.existsSync(dest)) {
147
+ fs.mkdirSync(dest, { recursive: true });
148
+ }
149
+
150
+ const files = fs.readdirSync(src);
151
+ for (const file of files) {
152
+ copyFolderRecursive(path.join(src, file), path.join(dest, file));
153
+ }
154
+ } else {
155
+ // Copy file and perform replacements if text-based
156
+ const ext = path.extname(src);
157
+ const textExtensions = ['.json', '.js', '.ts', '.tsx', '.css', '.html', '.md', '.toml', '.yml', '.yaml', '.example'];
158
+
159
+ if (textExtensions.includes(ext) || path.basename(src).startsWith('.')) {
160
+ let content = fs.readFileSync(src, 'utf8');
161
+
162
+ // Perform template replacements
163
+ content = content.replace(/thunder-monorepo/g, `${safeProjectName}-monorepo`);
164
+ content = content.replace(/@thunder\//g, `@${safeProjectName}/`);
165
+ content = content.replace(/thunder-api/g, `${safeProjectName}-api`);
166
+ content = content.replace(/Thunder Mobile App/g, `${capitalizedProjectName} Mobile App`);
167
+ content = content.replace(/thunder-mobile-app/g, `${safeProjectName}-mobile-app`);
168
+ content = content.replace(/"scheme": "thunder"/g, `"scheme": "${safeProjectName}"`);
169
+ content = content.replace(/com\.thunder\.app/g, `com.${safeProjectName}.app`);
170
+ content = content.replace(/admin@thunderstack\.dev/g, `admin@${safeProjectName}.dev`);
171
+
172
+ fs.writeFileSync(dest, content, 'utf8');
173
+ } else {
174
+ // Binary copy
175
+ fs.copyFileSync(src, dest);
176
+ }
177
+ }
178
+ }
179
+
180
+ console.log('Scaffolding files...');
181
+ copyFolderRecursive(sourceDir, targetDir);
182
+
183
+ // Set up directory junctions/symlinks for static assets mapping
184
+ const assetsSource = path.join(targetDir, 'client/assets');
185
+ const nextPublic = path.join(targetDir, 'client/next/public');
186
+ const expoAssets = path.join(targetDir, 'client/expo/assets');
187
+
188
+ console.log('Mapping shared public assets...');
189
+ createLink(nextPublic, assetsSource);
190
+ createLink(expoAssets, assetsSource);
191
+
192
+ // Copy .env.example to .env
193
+ const envExamplePath = path.join(targetDir, '.env.example');
194
+ const envPath = path.join(targetDir, '.env');
195
+ if (fs.existsSync(envExamplePath)) {
196
+ fs.copyFileSync(envExamplePath, envPath);
197
+ console.log('Created .env config file from template.');
198
+ }
199
+
200
+ // Automate dependency installation and asset mapping
201
+ console.log('\nInstalling dependencies and mapping shared assets... This may take a minute.');
202
+ try {
203
+ let installCmd = 'pnpm install';
204
+ try {
205
+ execSync('pnpm --version', { stdio: 'ignore' });
206
+ } catch (e) {
207
+ installCmd = 'npm install';
208
+ }
209
+
210
+ console.log(`Running "${installCmd}" in project directory: ${targetDir}`);
211
+ execSync(installCmd, { cwd: targetDir, stdio: 'inherit' });
212
+ console.log('\x1b[32m%s\x1b[0m', 'Dependencies installed and assets mapped successfully!');
213
+ } catch (err) {
214
+ console.error('\x1b[31m%s\x1b[0m', 'Warning: Failed to install dependencies automatically. You can install them manually using "pnpm install" or "npm install".');
215
+ }
216
+
217
+ console.log('\x1b[32m%s\x1b[0m', '\nProject successfully scaffolded!');
218
+ console.log('\nTo get started, run:');
219
+ console.log(` cd ${projectName}`);
220
+ console.log(' pnpm dev\n');
221
+
222
+ rl.close();
223
+ }
224
+
225
+ main().catch((err) => {
226
+ console.error('Error during scaffolding:', err);
227
+ rl.close();
228
+ process.exit(1);
229
+ });
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@thunder-stack/create-thunder-app",
3
+ "version": "0.0.1",
4
+ "description": "Scaffold a new THUNDER Stack cross-platform monorepo application",
5
+ "bin": {
6
+ "create-thunder-app": "./index.js"
7
+ },
8
+ "type": "module",
9
+ "engines": {
10
+ "node": ">=18.0.0"
11
+ },
12
+ "keywords": [
13
+ "solito",
14
+ "nextjs",
15
+ "expo",
16
+ "hono",
17
+ "drizzle",
18
+ "monorepo"
19
+ ],
20
+ "scripts": {
21
+ "prepublishOnly": "node prepublish.js",
22
+ "postpublish": "node postpublish.js"
23
+ },
24
+ "author": "THUNDER Stack",
25
+ "license": "MIT"
26
+ }
package/postpublish.js ADDED
@@ -0,0 +1,14 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ const templateDir = path.join(__dirname, 'template');
9
+
10
+ if (fs.existsSync(templateDir)) {
11
+ console.log('Cleaning up temporary template directory...');
12
+ fs.rmSync(templateDir, { recursive: true, force: true });
13
+ console.log('Cleanup complete.');
14
+ }
package/prepublish.js ADDED
@@ -0,0 +1,68 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ const rootDir = path.resolve(__dirname, '../..');
9
+ const templateDir = path.join(__dirname, 'template');
10
+
11
+ // Clean target first
12
+ if (fs.existsSync(templateDir)) {
13
+ fs.rmSync(templateDir, { recursive: true, force: true });
14
+ }
15
+ fs.mkdirSync(templateDir, { recursive: true });
16
+
17
+ const foldersToCopy = ['client', 'server', 'packages/tsconfig', 'scripts'];
18
+ const filesToCopy = [
19
+ 'package.json',
20
+ 'pnpm-workspace.yaml',
21
+ 'turbo.json',
22
+ '.gitignore',
23
+ '.env.example',
24
+ 'THUNDER_STACK.md',
25
+ 'README.md'
26
+ ];
27
+
28
+ function copyRecursive(src, dest) {
29
+ const lstats = fs.lstatSync(src);
30
+ if (lstats.isSymbolicLink()) {
31
+ // Skip symbolic links and junctions (they will be created fresh by the installer)
32
+ return;
33
+ }
34
+
35
+ if (lstats.isDirectory()) {
36
+ fs.mkdirSync(dest, { recursive: true });
37
+ const files = fs.readdirSync(src);
38
+ for (const file of files) {
39
+ if (file !== 'node_modules' && file !== 'dist' && file !== '.next' && file !== '.turbo' && file !== '.expo' && file !== 'web-build') {
40
+ copyRecursive(path.join(src, file), path.join(dest, file));
41
+ }
42
+ }
43
+ } else {
44
+ fs.copyFileSync(src, dest);
45
+ }
46
+ }
47
+
48
+ console.log('Packaging monorepo template files...');
49
+
50
+ // Copy folders
51
+ for (const folder of foldersToCopy) {
52
+ const src = path.join(rootDir, folder);
53
+ const dest = path.join(templateDir, folder);
54
+ if (fs.existsSync(src)) {
55
+ copyRecursive(src, dest);
56
+ }
57
+ }
58
+
59
+ // Copy files
60
+ for (const file of filesToCopy) {
61
+ const src = path.join(rootDir, file);
62
+ const dest = path.join(templateDir, file);
63
+ if (fs.existsSync(src)) {
64
+ fs.copyFileSync(src, dest);
65
+ }
66
+ }
67
+
68
+ console.log('Template packaging complete.');
@@ -0,0 +1,13 @@
1
+ # Database Connection URL (e.g. Neon PostgreSQL)
2
+ DATABASE_URL="postgres://username:password@hostname.neon.tech/dbname?sslmode=require"
3
+
4
+ # Better Auth Configuration
5
+ BETTER_AUTH_SECRET="your_better_auth_secret_here"
6
+ BETTER_AUTH_URL="http://localhost:8787"
7
+
8
+ # Public API URL (used by Next.js / Mobile clients to reach the Hono server)
9
+ NEXT_PUBLIC_API_URL="http://localhost:8787"
10
+
11
+ # OAuth Credentials (Google)
12
+ GOOGLE_CLIENT_ID="your_google_client_id.apps.googleusercontent.com"
13
+ GOOGLE_CLIENT_SECRET="your_google_client_secret"
@@ -0,0 +1 @@
1
+ # THUNDER Stack
@@ -0,0 +1,120 @@
1
+ # ⚡ THUNDER Stack Architecture & Development Guide ⚡
2
+
3
+ Welcome to the **THUNDER Stack**—a premium, type-safe, production-ready monorepo template built for scaffolding cross-platform applications with a shared codebase.
4
+
5
+ This document acts as a comprehensive reference guide explaining how the stack is structured, what technologies are utilized, and how to write clean cross-platform client code using **Solito**.
6
+
7
+ ---
8
+
9
+ ## 📂 Project Architecture
10
+
11
+ ```
12
+ thunder-monorepo/
13
+ ├── client/ # Frontend Applications
14
+ │ ├── expo/ # Mobile client (React Native / Expo iOS & Android)
15
+ │ ├── next/ # Web client (Next.js App Router)
16
+ │ └── shared/ # Shared views, components, and state logic (@thunder/app)
17
+
18
+ ├── server/ # Backend Services
19
+ │ ├── db/ # Database library & queries module (@thunder/db)
20
+ │ └── hono/ # API Gateway server (@thunder/api)
21
+
22
+ └── packages/ # Shared Dev Configs
23
+ └── tsconfig/ # Centralized TypeScript definitions (@thunder/tsconfig)
24
+ ```
25
+
26
+ ---
27
+
28
+ ## 🛠️ Tech Stack Tab Breakdown
29
+
30
+ Use the sections below to understand the backend, frontend, and cross-platform mechanisms:
31
+
32
+ ### Tab 1: Backend Layer (Hono & Drizzle Database)
33
+ * **API Gateway (`server/hono`):**
34
+ * Built using **Hono**, a light and fast web framework designed for Edge environments like Cloudflare Workers.
35
+ * Router mappings are located in `server/hono/src/index.ts`.
36
+ * Middleware manages CORS rules, routes mounting, and RBAC authentication checking.
37
+ * **Database Client (`server/db`):**
38
+ * Handled via **Drizzle ORM** (relational queries and SQL migrations).
39
+ * The PostgreSQL connection client connects to **Neon Database** (serverless Postgres) using HTTP connections via `@neondatabase/serverless` for fast, lightweight queries suited for Cloudflare Workers.
40
+ * **Schema Definition (`server/db/src/schema`):**
41
+ * **[auth.ts](file:///e:/College/Repos/paripoorna.me/server/db/src/schema/auth.ts)**: Declares Better Auth tables (`user`, `session`, `account`, `verification`) and Drizzle relationships.
42
+ * **[rbac.ts](file:///e:/College/Repos/paripoorna.me/server/db/src/schema/rbac.ts)**: Declares tables for Role-Based Access Control (`role`, `permission`, `rolePermission`, `userRole`).
43
+ * **Authentication Engine:**
44
+ * Driven by **Better Auth** mounted directly on Hono endpoint `/api/auth/*`. Session validation is handled using secure HTTP-only cookies.
45
+
46
+ ---
47
+
48
+ ### Tab 2: Frontend Layer (Solito, Next.js, and Expo)
49
+ * **Shared App Package (`client/shared`):**
50
+ * This is where **99% of your user interface is written**.
51
+ * Contains folders for `components`, `utils` (API client/auth), and `features` (reusable screens like Auth, Dashboard, User/Role Management).
52
+ * Expo and Next.js are thin wrapper shells that simply mount screens from this shared folder.
53
+ * **Next.js Web Wrapper (`client/next`):**
54
+ * Employs Next.js App Router.
55
+ * Root layout is set in `client/next/app/layout.tsx`.
56
+ * The main landing page in `client/next/app/page.tsx` just mounts the `<HomeScreen />` component from `@thunder/app`.
57
+ * **Expo Mobile Wrapper (`client/expo`):**
58
+ * Configured as an Expo Go compatible React Native application.
59
+ * Mounts `<HomeScreen />` from `@thunder/app` inside a `SafeAreaProvider` layout wrapper.
60
+
61
+ ---
62
+
63
+ ### Tab 3: Solito Cross-Platform Coding Guide
64
+ **Solito** bridges the gap between Next.js and React Native. It allows you to share views between web and native platforms seamlessly. However, writing code that behaves perfectly on both requires adherence to the following constraints:
65
+
66
+ #### 1. Avoid DOM-Specific or Native-Specific Modules
67
+ * **Web UI Elements:** Never use standard DOM elements like `<div>`, `<span>`, `<p>`, or `<a>` in your shared code. They will crash on iOS and Android. Instead, use React Native's `<View>`, `<Text>`, and `<TouchableOpacity>`.
68
+ * **Web Globals:** Avoid direct access to browser objects like `window`, `document`, or `localStorage` in top-level code. If you must use them, wrap them in checks like `Platform.OS === 'web'` or run them inside `useEffect()`.
69
+
70
+ #### 2. Cross-Platform Styling (NativeWind)
71
+ * Styling uses **NativeWind (v4)**, allowing Tailwind CSS classes to be translated into React Native StyleSheet objects.
72
+ * Always use shared layout values:
73
+ * Flex layouts: Use `flex-row`, `flex-wrap`, `items-center`, `justify-between`.
74
+ * Spacing & Sizing: Use standard utility names (e.g. `p-6`, `gap-4`, `w-full`).
75
+ * Web-specific grid layouts can be achieved with responsive modifiers: e.g. `className="w-full md:w-64"`.
76
+
77
+ #### 3. Navigation between screens
78
+ * Never import `next/router` or use React Navigation hooks directly in the shared feature screens.
79
+ * Always import and use **`solito/navigation`**:
80
+ ```typescript
81
+ import { useRouter } from "solito/navigation";
82
+
83
+ const router = useRouter();
84
+ router.push("/management/users");
85
+ ```
86
+ Solito automatically translates `router.push` to `next/navigation` on Web and React Navigation actions on Mobile.
87
+
88
+ #### 4. Package Transpilation in Next.js
89
+ * Web applications require package transpilation for Native-only libraries (e.g. `react-native-web`, `moti`, `react-native-reanimated`).
90
+ * If you install any external library containing React Native components, ensure you add it to `transpilePackages` inside [next.config.js](file:///e:/College/Repos/paripoorna.me/client/next/next.config.js).
91
+
92
+ #### 5. Layout & Element Debugging
93
+ * Always test animations using **Moti** (built on Reanimated), as standard CSS transitions do not compile on iOS/Android.
94
+ * Keep standard layout sizes predictable. When using lists, implement native scrolling wrappers like `<ScrollView>` and set standard sizing cards to auto-adjust using responsive flex properties.
95
+
96
+ ---
97
+
98
+ ### Tab 4: Monorepo CLI Scripts & Commands
99
+ Manage your local environment, code checks, and serverless builds using the following root scripts:
100
+
101
+ #### 1. Clean & Installation
102
+ * **`pnpm clean`**: Deletes all temporary build files (`.turbo`, `.next`, `.expo`, `dist`, `web-build`) and `node_modules` recursively across the monorepo.
103
+ * **`pnpm clean:install`**: Cleans the repository and reinstalls all dependencies fresh.
104
+
105
+ #### 2. Development & Checks
106
+ * **`pnpm dev`**: Starts all development servers (Next.js, Expo, Hono) simultaneously with hot-reloading.
107
+ * **`pnpm build`**: Prepares production builds for all client wrappers.
108
+ * **`pnpm typecheck`**: Runs compiler diagnostics to catch TypeScript issues globally.
109
+ * **`pnpm lint`**: Enforces unified workspace lint configurations.
110
+ * **`pnpm test`**: Runs unit test suites using Jest/Vitest.
111
+
112
+ #### 3. Database & Seeding
113
+ * **`pnpm db:generate`**: Inspects Drizzle schemas and creates SQL migration scripts.
114
+ * **`pnpm db:migrate`**: Applies migrations to your cloud PostgreSQL instance.
115
+ * **`pnpm db:seed`**: Populates default Roles, Permissions, and test dummy users in the database.
116
+
117
+ #### 4. Prettier Formatting
118
+ * **`pnpm format`**: Standardizes style alignments on all source files.
119
+ * **`pnpm format:check`**: Verifies formatting health without rewriting source contents.
120
+
@@ -0,0 +1,36 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%">
2
+ <!-- Dark Sleek Background Canvas -->
3
+ <rect width="512" height="512" rx="96" fill="#0F172A"/>
4
+
5
+ <!-- Subtle inner gradient glow -->
6
+ <defs>
7
+ <linearGradient id="thunderGlow" x1="0%" y1="0%" x2="100%" y2="100%">
8
+ <stop offset="0%" stop-color="#38BDF8"/>
9
+ <stop offset="100%" stop-color="#8B5CF6"/>
10
+ </linearGradient>
11
+ <linearGradient id="boltGrad" x1="0%" y1="0%" x2="50%" y2="100%">
12
+ <stop offset="0%" stop-color="#FEE2E2"/>
13
+ <stop offset="30%" stop-color="#FDE047"/>
14
+ <stop offset="100%" stop-color="#EAB308"/>
15
+ </linearGradient>
16
+ </defs>
17
+
18
+ <!-- Geometric Accent Lines mirroring the Monorepo/Unified structure -->
19
+ <path d="M64 160 H448 M64 352 H448" stroke="#1E293B" stroke-width="8" stroke-dasharray="16 12"/>
20
+ <path d="M160 64 V448 M352 64 V448" stroke="#1E293B" stroke-width="8" stroke-dasharray="16 12"/>
21
+
22
+ <!-- The THUNDER Bolt (Scaled and shifted slightly upward to avoid text overlap) -->
23
+ <path d="M288 48 L128 272 H256 L224 464 L384 240 H256 Z"
24
+ fill="url(#boltGrad)"
25
+ transform="translate(256, 230) scale(0.8) translate(-256, -256)"
26
+ filter="drop-shadow(0px 8px 24px rgba(234, 179, 8, 0.4))"/>
27
+
28
+ <!-- Subtle branding text integrated cleanly at the bottom -->
29
+ <text x="256" y="475"
30
+ font-family="system-ui, -apple-system, sans-serif"
31
+ font-size="28"
32
+ font-weight="900"
33
+ fill="#64748B"
34
+ letter-spacing="6"
35
+ text-anchor="middle">THUNDER</text>
36
+ </svg>
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+ import App from "./App";
4
+
5
+ // Mock the shared screen layout to isolate wrapper testing
6
+ jest.mock("@thunder/app", () => {
7
+ const { View, Text } = require("react-native");
8
+ return {
9
+ HomeScreen: () => (
10
+ <View>
11
+ <Text>Thunder Test</Text>
12
+ </View>
13
+ ),
14
+ };
15
+ });
16
+
17
+ test("renders the Expo App wrapper with the shared home screen", () => {
18
+ const { getByText } = render(<App />);
19
+ expect(getByText("Thunder Test")).toBeTruthy();
20
+ });
@@ -0,0 +1,16 @@
1
+ import "./global.css";
2
+ import React from "react";
3
+ import { StatusBar } from "expo-status-bar";
4
+ import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
5
+ import { HomeScreen } from "@thunder/app";
6
+
7
+ export default function App() {
8
+ return (
9
+ <SafeAreaProvider>
10
+ <SafeAreaView className="flex-1 bg-slate-950">
11
+ <StatusBar style="light" />
12
+ <HomeScreen />
13
+ </SafeAreaView>
14
+ </SafeAreaProvider>
15
+ );
16
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "expo": {
3
+ "name": "Thunder Mobile App",
4
+ "slug": "thunder-mobile-app",
5
+ "version": "1.0.0",
6
+ "orientation": "portrait",
7
+ "icon": "./assets/logo.png",
8
+ "userInterfaceStyle": "dark",
9
+ "scheme": "thunder",
10
+ "splash": {
11
+ "image": "./assets/logo.png",
12
+ "resizeMode": "contain",
13
+ "backgroundColor": "#090d16"
14
+ },
15
+ "ios": {
16
+ "supportsTablet": true,
17
+ "bundleIdentifier": "com.thunder.app"
18
+ },
19
+ "android": {
20
+ "adaptiveIcon": {
21
+ "foregroundImage": "./assets/logo.png",
22
+ "backgroundColor": "#090d16"
23
+ },
24
+ "package": "com.thunder.app"
25
+ },
26
+ "web": {
27
+ "favicon": "./assets/logo.svg"
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,12 @@
1
+ module.exports = function (api) {
2
+ api.cache(true);
3
+ return {
4
+ presets: [
5
+ ["babel-preset-expo", { jsxImportSource: "nativewind" }],
6
+ "nativewind/babel",
7
+ ],
8
+ plugins: [
9
+ "react-native-reanimated/plugin",
10
+ ],
11
+ };
12
+ };
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ preset: "jest-expo",
3
+ transformIgnorePatterns: [
4
+ "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|solito)",
5
+ ],
6
+ setupFilesAfterEnv: ["@testing-library/react-native/extend-expect"],
7
+ };
@@ -0,0 +1,21 @@
1
+ const { getDefaultConfig } = require("expo/metro-config");
2
+ const path = require("path");
3
+
4
+ const projectRoot = __dirname;
5
+ const workspaceRoot = path.resolve(projectRoot, "../..");
6
+
7
+ const config = getDefaultConfig(projectRoot);
8
+
9
+ // 1. Watch all files in the monorepo workspace (including client/shared)
10
+ config.watchFolders = [workspaceRoot];
11
+
12
+ // 2. Let Metro locate dependencies (inside root node_modules and local node_modules)
13
+ config.resolver.nodeModulesPaths = [
14
+ path.resolve(projectRoot, "node_modules"),
15
+ path.resolve(workspaceRoot, "node_modules"),
16
+ ];
17
+
18
+ // 3. Disable hierarchical lookup to prevent duplicate React instances
19
+ config.resolver.disableHierarchicalLookup = true;
20
+
21
+ module.exports = config;
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "expo-mobile-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "start": "expo start",
7
+ "android": "expo start --android",
8
+ "ios": "expo start --ios",
9
+ "web": "expo start --web",
10
+ "test": "jest"
11
+ },
12
+ "dependencies": {
13
+ "@thunder/app": "workspace:*",
14
+ "expo": "~51.0.39",
15
+ "expo-status-bar": "~1.12.1",
16
+ "moti": "^0.28.1",
17
+ "nativewind": "^4.2.6",
18
+ "react": "18.2.0",
19
+ "react-native": "0.74.1",
20
+ "react-native-reanimated": "~3.10.1",
21
+ "react-native-safe-area-context": "4.10.1",
22
+ "react-native-screens": "3.31.1"
23
+ },
24
+ "devDependencies": {
25
+ "@babel/core": "^7.29.7",
26
+ "@testing-library/react-native": "^13.3.3",
27
+ "@types/jest": "^29.5.14",
28
+ "jest": "^29.7.0",
29
+ "jest-expo": "~51.0.4",
30
+ "react-test-renderer": "18.2.0",
31
+ "typescript": "^5.9.3"
32
+ }
33
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "expo/tsconfig.base",
3
+ "compilerOptions": {
4
+ "strict": true
5
+ }
6
+ }