@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.
- package/index.js +229 -0
- package/package.json +26 -0
- package/postpublish.js +14 -0
- package/prepublish.js +68 -0
- package/template/.env.example +13 -0
- package/template/README.md +1 -0
- package/template/THUNDER_STACK.md +120 -0
- package/template/client/assets/logo.svg +36 -0
- package/template/client/expo/App.test.tsx +20 -0
- package/template/client/expo/App.tsx +16 -0
- package/template/client/expo/app.json +30 -0
- package/template/client/expo/babel.config.js +12 -0
- package/template/client/expo/global.css +3 -0
- package/template/client/expo/jest.config.js +7 -0
- package/template/client/expo/metro.config.js +21 -0
- package/template/client/expo/package.json +33 -0
- package/template/client/expo/tsconfig.json +6 -0
- package/template/client/next/app/globals.css +14 -0
- package/template/client/next/app/layout.tsx +28 -0
- package/template/client/next/app/page.test.tsx +13 -0
- package/template/client/next/app/page.tsx +10 -0
- package/template/client/next/next-env.d.ts +5 -0
- package/template/client/next/next.config.js +21 -0
- package/template/client/next/package.json +39 -0
- package/template/client/next/playwright.config.ts +17 -0
- package/template/client/next/postcss.config.js +6 -0
- package/template/client/next/tsconfig.json +30 -0
- package/template/client/next/vitest.config.ts +11 -0
- package/template/client/next/vitest.setup.ts +1 -0
- package/template/client/shared/index.ts +2 -0
- package/template/client/shared/package.json +24 -0
- package/template/client/shared/src/components/Button.tsx +38 -0
- package/template/client/shared/src/components/Card.tsx +17 -0
- package/template/client/shared/src/features/auth/login.tsx +139 -0
- package/template/client/shared/src/features/dashboard/index.tsx +232 -0
- package/template/client/shared/src/features/home/screen.tsx +212 -0
- package/template/client/shared/src/features/management/roles.tsx +356 -0
- package/template/client/shared/src/features/management/users.tsx +245 -0
- package/template/client/shared/src/nativewind-env.d.ts +1 -0
- package/template/client/shared/src/utils/auth.ts +21 -0
- package/template/client/shared/tailwind.config.js +19 -0
- package/template/client/shared/tsconfig.json +9 -0
- package/template/package.json +47 -0
- package/template/packages/tsconfig/base.json +20 -0
- package/template/packages/tsconfig/package.json +11 -0
- package/template/pnpm-workspace.yaml +4 -0
- package/template/scripts/clean.js +56 -0
- package/template/server/db/drizzle.config.ts +10 -0
- package/template/server/db/migrations/0000_loving_mindworm.sql +86 -0
- package/template/server/db/migrations/meta/0000_snapshot.json +549 -0
- package/template/server/db/migrations/meta/_journal.json +13 -0
- package/template/server/db/package.json +28 -0
- package/template/server/db/src/index.ts +16 -0
- package/template/server/db/src/migrate.ts +28 -0
- package/template/server/db/src/schema/auth.ts +73 -0
- package/template/server/db/src/schema/index.ts +2 -0
- package/template/server/db/src/schema/rbac.ts +72 -0
- package/template/server/db/src/schema.ts +1 -0
- package/template/server/db/src/seed.ts +204 -0
- package/template/server/db/src/services/rbac.ts +144 -0
- package/template/server/db/src/services/user.ts +34 -0
- package/template/server/db/src/types/index.ts +34 -0
- package/template/server/db/tsconfig.json +8 -0
- package/template/server/hono/package.json +22 -0
- package/template/server/hono/src/auth.ts +29 -0
- package/template/server/hono/src/index.ts +215 -0
- package/template/server/hono/tsconfig.json +8 -0
- package/template/server/hono/wrangler.toml +12 -0
- 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,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
|
+
}
|