create-laju-app 1.0.15 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -266
- package/bin/cli.js +174 -53
- package/package.json +22 -8
package/README.md
CHANGED
|
@@ -1,309 +1,149 @@
|
|
|
1
1
|
# Laju
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
⚡ **High-performance TypeScript web framework** - 11x faster than Express.js
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Build modern full-stack applications with HyperExpress, Svelte 5, and Inertia.js.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
[](https://github.com/maulanashalihin/laju)
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
- Modern frontend with Svelte 5
|
|
11
|
-
- TypeScript support for better type safety
|
|
12
|
-
- Inertia.js integration for seamless client-server communication
|
|
13
|
-
- Built-in authentication system
|
|
14
|
-
- SQLite database with Knex query builder
|
|
15
|
-
- Email support with Nodemailer
|
|
16
|
-
- Google APIs integration
|
|
17
|
-
- Redis caching support
|
|
18
|
-
- Asset bundling with Vite
|
|
19
|
-
- TailwindCSS for styling
|
|
12
|
+
## 🚀 Quick Start
|
|
20
13
|
|
|
21
|
-
## Prerequisites
|
|
22
|
-
|
|
23
|
-
- Node.js (Latest LTS version recommended)
|
|
24
|
-
- npm or yarn
|
|
25
|
-
- Redis server (optional, for caching)
|
|
26
|
-
|
|
27
|
-
## Installation
|
|
28
|
-
|
|
29
|
-
Run the following command
|
|
30
14
|
```bash
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
# Create new project
|
|
16
|
+
npx create-laju-app my-project
|
|
17
|
+
cd my-project
|
|
34
18
|
|
|
35
|
-
|
|
19
|
+
# Setup database
|
|
20
|
+
cp .env.example .env
|
|
21
|
+
npx knex migrate:latest
|
|
36
22
|
|
|
37
|
-
|
|
23
|
+
# Start development
|
|
38
24
|
npm run dev
|
|
39
25
|
```
|
|
40
26
|
|
|
41
|
-
|
|
42
|
-
- Start the Vite development server for frontend assets
|
|
43
|
-
- Run the backend server with nodemon for auto-reloading
|
|
44
|
-
|
|
45
|
-
## Building for Production
|
|
46
|
-
|
|
47
|
-
To build the application for production:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npm run build
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
This command will:
|
|
54
|
-
- Clean the build directory
|
|
55
|
-
- Build frontend assets with Vite
|
|
56
|
-
- Compile TypeScript files
|
|
57
|
-
- Copy necessary files to the build directory
|
|
58
|
-
|
|
59
|
-
## Project Structure
|
|
60
|
-
|
|
61
|
-
- `/app` - Core application files
|
|
62
|
-
- `/middlewares` - Custom middleware functions
|
|
63
|
-
- `/services` - Service layer implementations
|
|
64
|
-
- `/controllers` - Application controllers
|
|
65
|
-
- `/resources` - Frontend resources
|
|
66
|
-
- `/views` - Svelte components and views
|
|
67
|
-
- `/js` - JavaScript assets and modules
|
|
68
|
-
- `/routes` - Route definitions
|
|
69
|
-
- `/migrations` - Database migrations
|
|
70
|
-
- `/public` - Static files
|
|
71
|
-
- `/dist` - Compiled assets (generated)
|
|
72
|
-
- `/build` - Production build output
|
|
73
|
-
|
|
74
|
-
## Key Dependencies
|
|
75
|
-
|
|
76
|
-
### Backend
|
|
77
|
-
- HyperExpress - High-performance web server
|
|
78
|
-
- Knex - SQL query builder
|
|
79
|
-
- SQLite3 - Database
|
|
80
|
-
- Nodemailer - Email sending
|
|
81
|
-
- Redis - Caching (optional)
|
|
82
|
-
|
|
83
|
-
### Frontend
|
|
84
|
-
- Svelte 5 - UI framework
|
|
85
|
-
- Inertia.js - Client-server communication
|
|
86
|
-
- TailwindCSS - Utility-first CSS framework
|
|
87
|
-
- Vite - Build tool and dev server
|
|
88
|
-
|
|
89
|
-
## Scripts
|
|
90
|
-
|
|
91
|
-
- `npm run dev` - Start development server
|
|
92
|
-
- `npm run build` - Build for production
|
|
93
|
-
|
|
94
|
-
## Contributing
|
|
95
|
-
|
|
96
|
-
1. Fork the repository
|
|
97
|
-
2. Create your feature branch
|
|
98
|
-
3. Commit your changes
|
|
99
|
-
4. Push to the branch
|
|
100
|
-
5. Create a new Pull Request
|
|
101
|
-
|
|
102
|
-
## License
|
|
103
|
-
|
|
104
|
-
ISC License
|
|
105
|
-
|
|
106
|
-
## Tutorial: Building Your First App
|
|
27
|
+
Visit `http://localhost:5555`
|
|
107
28
|
|
|
108
|
-
|
|
29
|
+
## ✨ Features
|
|
109
30
|
|
|
110
|
-
###
|
|
31
|
+
### Performance First
|
|
32
|
+
- **258,611 req/sec** - HyperExpress server (11x faster than Express)
|
|
33
|
+
- **19.9x faster writes** - SQLite with WAL mode
|
|
34
|
+
- **Zero-config caching** - Database cache included (optional Redis)
|
|
111
35
|
|
|
112
|
-
|
|
36
|
+
### Modern Stack
|
|
37
|
+
- **Svelte 5** - Reactive UI with runes
|
|
38
|
+
- **Inertia.js** - SPA without client-side routing
|
|
39
|
+
- **TailwindCSS 4** - Utility-first CSS with Vite
|
|
40
|
+
- **TypeScript** - Full type safety
|
|
113
41
|
|
|
114
|
-
|
|
42
|
+
### Built-in Services
|
|
43
|
+
- **Authentication** - Sessions, OAuth (Google), password reset
|
|
44
|
+
- **Storage** - S3/Wasabi with presigned URLs
|
|
45
|
+
- **Email** - Nodemailer (SMTP) or Resend (API)
|
|
46
|
+
- **Caching** - Database cache or Redis
|
|
47
|
+
- **Templates** - Eta for SSR
|
|
115
48
|
|
|
116
|
-
|
|
117
|
-
import { Request, Response } from "../../type";
|
|
118
|
-
import DB from "../services/DB";
|
|
49
|
+
## 📊 Performance
|
|
119
50
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
51
|
+
| Framework | Requests/sec | Comparison |
|
|
52
|
+
|-----------|--------------|------------|
|
|
53
|
+
| **Laju** | **258,611** | Baseline |
|
|
54
|
+
| Pure Node.js | 124,024 | 2x slower |
|
|
55
|
+
| Express.js | 22,590 | 11x slower |
|
|
56
|
+
| Laravel | 80 | 3,232x slower |
|
|
125
57
|
|
|
126
|
-
|
|
127
|
-
return response.inertia("posts/create");
|
|
128
|
-
}
|
|
58
|
+
*Benchmark: Simple JSON response on same hardware*
|
|
129
59
|
|
|
130
|
-
|
|
131
|
-
const { title, content } = request.body;
|
|
132
|
-
|
|
133
|
-
await DB.table("posts").insert({
|
|
134
|
-
title,
|
|
135
|
-
content,
|
|
136
|
-
created_at: Date.now(),
|
|
137
|
-
updated_at: Date.now()
|
|
138
|
-
});
|
|
60
|
+
## 📚 Documentation
|
|
139
61
|
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
}
|
|
62
|
+
**[Complete Documentation →](https://github.com/maulanashalihin/laju/tree/main/docs)**
|
|
143
63
|
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
2. Add routes in `routes/web.ts`:
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
import PostController from "../app/controllers/PostController";
|
|
151
|
-
|
|
152
|
-
// Add these routes with your existing routes
|
|
153
|
-
Route.get("/posts", PostController.index);
|
|
154
|
-
Route.get("/posts/create", PostController.create);
|
|
155
|
-
Route.post("/posts", PostController.store);
|
|
156
|
-
```
|
|
64
|
+
Documentation is organized for progressive learning from beginner to advanced.
|
|
157
65
|
|
|
158
|
-
###
|
|
66
|
+
### Getting Started
|
|
67
|
+
- [Introduction](https://github.com/maulanashalihin/laju/blob/main/docs/01-INTRODUCTION.md) - Framework overview, quick start
|
|
68
|
+
- [Project Structure](https://github.com/maulanashalihin/laju/blob/main/docs/02-PROJECT-STRUCTURE.md) - Directory layout
|
|
69
|
+
- [Database](https://github.com/maulanashalihin/laju/blob/main/docs/03-DATABASE.md) - Knex.js + SQLite
|
|
159
70
|
|
|
160
|
-
|
|
71
|
+
### Core Features
|
|
72
|
+
- [Routing & Controllers](https://github.com/maulanashalihin/laju/blob/main/docs/04-ROUTING-CONTROLLERS.md) - Handle requests
|
|
73
|
+
- [Frontend (Svelte 5)](https://github.com/maulanashalihin/laju/blob/main/docs/05-FRONTEND-SVELTE.md) - Build reactive UI
|
|
74
|
+
- [Authentication](https://github.com/maulanashalihin/laju/blob/main/docs/06-AUTHENTICATION.md) - Sessions + OAuth
|
|
75
|
+
- [Middleware](https://github.com/maulanashalihin/laju/blob/main/docs/07-MIDDLEWARE.md) - Auth, rate limiting
|
|
76
|
+
- [Validation](https://github.com/maulanashalihin/laju/blob/main/docs/08-VALIDATION.md) - Input validation
|
|
77
|
+
- [Email](https://github.com/maulanashalihin/laju/blob/main/docs/09-EMAIL.md) - Send emails
|
|
161
78
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
79
|
+
### Advanced Features
|
|
80
|
+
- [Storage (S3)](https://github.com/maulanashalihin/laju/blob/main/docs/10-STORAGE.md) - File uploads
|
|
81
|
+
- [Caching](https://github.com/maulanashalihin/laju/blob/main/docs/11-CACHING.md) - Redis + Database cache
|
|
82
|
+
- [Background Jobs](https://github.com/maulanashalihin/laju/blob/main/docs/12-BACKGROUND-JOBS.md) - Cron jobs, Scheduling
|
|
83
|
+
- [CSRF Protection](https://github.com/maulanashalihin/laju/blob/main/docs/13-CSRF.md) - Security
|
|
84
|
+
- [Translation](https://github.com/maulanashalihin/laju/blob/main/docs/14-TRANSLATION.md) - Multi-language
|
|
167
85
|
|
|
168
|
-
|
|
169
|
-
|
|
86
|
+
### Production
|
|
87
|
+
- [Best Practices](https://github.com/maulanashalihin/laju/blob/main/docs/16-BEST-PRACTICES.md) - Code quality
|
|
88
|
+
- [Security Guide](https://github.com/maulanashalihin/laju/blob/main/docs/17-SECURITY.md) - Secure your app
|
|
89
|
+
- [Testing](https://github.com/maulanashalihin/laju/blob/main/docs/19-TESTING.md) - Unit + Integration tests
|
|
90
|
+
- [Deployment](https://github.com/maulanashalihin/laju/blob/main/docs/20-DEPLOYMENT.md) - Production setup
|
|
170
91
|
|
|
171
|
-
|
|
172
|
-
await knex.schema.createTable('posts', function (table) {
|
|
173
|
-
table.increments('id').primary();
|
|
174
|
-
table.string('title').notNullable();
|
|
175
|
-
table.text('content').notNullable();
|
|
176
|
-
table.bigInteger('created_at');
|
|
177
|
-
table.bigInteger('updated_at');
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export async function down(knex: Knex): Promise<void> {
|
|
182
|
-
await knex.schema.dropTable('posts');
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
Run the migration:
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
npx knex migrate:latest
|
|
190
|
-
```
|
|
92
|
+
## Project Structure
|
|
191
93
|
|
|
192
|
-
### 3. Creating Svelte Components
|
|
193
|
-
|
|
194
|
-
1. Create `resources/views/posts/index.svelte`:
|
|
195
|
-
|
|
196
|
-
```svelte
|
|
197
|
-
<script>
|
|
198
|
-
export let posts = [];
|
|
199
|
-
</script>
|
|
200
|
-
|
|
201
|
-
<div class="max-w-4xl mx-auto p-4">
|
|
202
|
-
<div class="flex justify-between items-center mb-6">
|
|
203
|
-
<h1 class="text-2xl font-bold">Blog Posts</h1>
|
|
204
|
-
<a
|
|
205
|
-
href="/posts/create"
|
|
206
|
-
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
|
|
207
|
-
>
|
|
208
|
-
Create Post
|
|
209
|
-
</a>
|
|
210
|
-
</div>
|
|
211
|
-
|
|
212
|
-
<div class="space-y-4">
|
|
213
|
-
{#each posts as post}
|
|
214
|
-
<div class="border p-4 rounded">
|
|
215
|
-
<h2 class="text-xl font-semibold">{post.title}</h2>
|
|
216
|
-
<p class="mt-2 text-gray-600">{post.content}</p>
|
|
217
|
-
</div>
|
|
218
|
-
{/each}
|
|
219
|
-
</div>
|
|
220
|
-
</div>
|
|
221
94
|
```
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
</script>
|
|
238
|
-
|
|
239
|
-
<div class="max-w-4xl mx-auto p-4">
|
|
240
|
-
<h1 class="text-2xl font-bold mb-6">Create New Post</h1>
|
|
241
|
-
|
|
242
|
-
<form on:submit|preventDefault={handleSubmit} class="space-y-4">
|
|
243
|
-
<div>
|
|
244
|
-
<label class="block text-sm font-medium mb-1">Title</label>
|
|
245
|
-
<input
|
|
246
|
-
type="text"
|
|
247
|
-
bind:value={form.title}
|
|
248
|
-
class="w-full px-3 py-2 border rounded"
|
|
249
|
-
/>
|
|
250
|
-
</div>
|
|
251
|
-
|
|
252
|
-
<div>
|
|
253
|
-
<label class="block text-sm font-medium mb-1">Content</label>
|
|
254
|
-
<textarea
|
|
255
|
-
bind:value={form.content}
|
|
256
|
-
class="w-full px-3 py-2 border rounded h-32"
|
|
257
|
-
></textarea>
|
|
258
|
-
</div>
|
|
259
|
-
|
|
260
|
-
<div>
|
|
261
|
-
<button
|
|
262
|
-
type="submit"
|
|
263
|
-
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
|
|
264
|
-
>
|
|
265
|
-
Create Post
|
|
266
|
-
</button>
|
|
267
|
-
</div>
|
|
268
|
-
</form>
|
|
269
|
-
</div>
|
|
95
|
+
app/
|
|
96
|
+
├── controllers/ # Request handlers
|
|
97
|
+
├── middlewares/ # Auth, rate limiting
|
|
98
|
+
├── services/ # DB, Mailer, Storage
|
|
99
|
+
└── validators/ # Input validation
|
|
100
|
+
|
|
101
|
+
resources/
|
|
102
|
+
├── js/
|
|
103
|
+
│ ├── Pages/ # Svelte/Inertia pages
|
|
104
|
+
│ ├── Components/ # Reusable components
|
|
105
|
+
│ └── index.css # TailwindCSS 4
|
|
106
|
+
└── views/ # Eta templates
|
|
107
|
+
|
|
108
|
+
routes/ # Route definitions
|
|
109
|
+
migrations/ # Database migrations
|
|
270
110
|
```
|
|
271
111
|
|
|
272
|
-
|
|
112
|
+
## Commands
|
|
273
113
|
|
|
274
|
-
1. Start the development server:
|
|
275
114
|
```bash
|
|
276
|
-
npm run dev
|
|
115
|
+
npm run dev # Development
|
|
116
|
+
npm run build # Production build
|
|
117
|
+
node laju make:controller UserController # Generate controller
|
|
118
|
+
npx knex migrate:make create_posts # Create migration
|
|
119
|
+
npx knex migrate:latest # Run migrations
|
|
277
120
|
```
|
|
278
121
|
|
|
279
|
-
|
|
280
|
-
3. Try creating a new post using the form
|
|
281
|
-
4. View the list of posts on the index page
|
|
122
|
+
## Tech Stack
|
|
282
123
|
|
|
283
|
-
|
|
124
|
+
| Layer | Technology |
|
|
125
|
+
|-------|------------|
|
|
126
|
+
| Server | HyperExpress |
|
|
127
|
+
| Database | BetterSQLite3 + Knex |
|
|
128
|
+
| Frontend | Svelte 5 + Inertia.js |
|
|
129
|
+
| Styling | TailwindCSS 4 |
|
|
130
|
+
| Build | Vite |
|
|
131
|
+
| Templates | Eta |
|
|
284
132
|
|
|
285
|
-
|
|
286
|
-
2. **Controllers**: Handle business logic and return Inertia responses
|
|
287
|
-
3. **Database**: Use Knex.js for database operations and migrations
|
|
288
|
-
4. **Frontend**: Svelte components with Inertia.js for seamless page transitions
|
|
289
|
-
5. **Styling**: TailwindCSS for utility-first styling
|
|
133
|
+
## Author
|
|
290
134
|
|
|
291
|
-
|
|
135
|
+
**Maulana Shalihin** - [maulana@drip.id](mailto:maulana@drip.id)
|
|
292
136
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
- Database migrations in `migrations`
|
|
137
|
+
- [tapsite.ai](https://tapsite.ai) - AI website builder
|
|
138
|
+
- [dripsender.id](https://dripsender.id) - Email marketing
|
|
139
|
+
- [laju.dev](https://laju.dev) - This framework
|
|
297
140
|
|
|
298
|
-
|
|
299
|
-
- Use TypeScript types for better type safety
|
|
300
|
-
- Keep controllers focused on single responsibilities
|
|
301
|
-
- Use Inertia.js for state management between server and client
|
|
141
|
+
## Support
|
|
302
142
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
- Include timestamps for tracking record changes
|
|
143
|
+
- Star this repository
|
|
144
|
+
- [Become a Sponsor](https://github.com/sponsors/maulanashalihin)
|
|
145
|
+
- Report bugs via [GitHub Issues](https://github.com/maulanashalihin/laju/issues)
|
|
307
146
|
|
|
308
|
-
|
|
147
|
+
## License
|
|
309
148
|
|
|
149
|
+
MIT License
|
package/bin/cli.js
CHANGED
|
@@ -7,22 +7,99 @@ const path = require('path');
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const { execSync } = require('child_process');
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
10
|
+
function detectAvailablePackageManagers() {
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
const packageManagers = ['bun', 'yarn', 'npm'];
|
|
13
|
+
const available = [];
|
|
14
|
+
|
|
15
|
+
for (const pm of packageManagers) {
|
|
16
|
+
try {
|
|
17
|
+
execSync(`${pm} --version`, { stdio: 'ignore' });
|
|
18
|
+
available.push(pm);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return available.length > 0 ? available : ['npm'];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function selectPackageManager(available) {
|
|
27
|
+
if (available.length === 1) {
|
|
28
|
+
return available[0];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log('\x1b[36m Which package manager would you like to use?\x1b[0m');
|
|
33
|
+
|
|
34
|
+
const response = await prompts({
|
|
35
|
+
type: 'select',
|
|
36
|
+
name: 'packageManager',
|
|
37
|
+
message: '',
|
|
38
|
+
choices: available.map(pm => ({
|
|
39
|
+
title: pm === 'npm' ? 'npm (stable ✓)' : pm === 'yarn' ? 'yarn (fast ✓)' : 'bun (experimental ⚠️)',
|
|
40
|
+
value: pm
|
|
41
|
+
}))
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const selected = response.packageManager || 'npm';
|
|
45
|
+
|
|
46
|
+
if (selected === 'bun') {
|
|
47
|
+
console.log('\x1b[90m Note: Bun is experimental. If you encounter issues, try npm or yarn.\x1b[0m');
|
|
48
|
+
console.log('');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return selected;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getInstallCommand(packageManager) {
|
|
55
|
+
switch (packageManager) {
|
|
56
|
+
case 'bun':
|
|
57
|
+
return 'bun install';
|
|
58
|
+
case 'yarn':
|
|
59
|
+
return 'yarn';
|
|
60
|
+
case 'npm':
|
|
61
|
+
default:
|
|
62
|
+
return 'npm install';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getRunCommand(packageManager) {
|
|
67
|
+
switch (packageManager) {
|
|
68
|
+
case 'bun':
|
|
69
|
+
return 'bun run';
|
|
70
|
+
case 'yarn':
|
|
71
|
+
return 'yarn';
|
|
72
|
+
case 'npm':
|
|
73
|
+
default:
|
|
74
|
+
return 'npm run';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ASCII_ART = `
|
|
79
|
+
\x1b[38;5;208m
|
|
80
|
+
++++++++++++++++++
|
|
81
|
+
+++++++++++++++++++
|
|
82
|
+
+++++++++++++++++++
|
|
83
|
+
+++++++++++++++++++
|
|
84
|
+
+++++++++++++++++++
|
|
85
|
+
++++++++++++++++++++
|
|
86
|
+
+++++++++++++++++++
|
|
87
|
+
+++++++++++++++++++
|
|
88
|
+
+++++++++++++++++++
|
|
89
|
+
+++++++++++++++++++
|
|
90
|
+
+++++++++++++++++++
|
|
91
|
+
|
|
92
|
+
+++++++++++++++++++++++++++++++++
|
|
93
|
+
++++++++++++++++++++++++++++++++++
|
|
94
|
+
+++++++++++++++++++++++++++++++++
|
|
95
|
+
++++++++++++++++++++++++++++++++++
|
|
96
|
+
++++++++++++++++++++++++++++++++++
|
|
97
|
+
++++++++++++++++++++++++++++++++++
|
|
98
|
+
+++++++++++++++++++++++++++++++++
|
|
99
|
+
+++++++++++++++++++++++++++++++++
|
|
100
|
+
++++++++++++++++++++++++++++++++
|
|
101
|
+
\x1b[0m
|
|
102
|
+
`;
|
|
26
103
|
|
|
27
104
|
program
|
|
28
105
|
.name('create-laju-app')
|
|
@@ -31,22 +108,48 @@ program
|
|
|
31
108
|
|
|
32
109
|
program
|
|
33
110
|
.argument('[project-directory]', 'Project directory name')
|
|
34
|
-
.
|
|
111
|
+
.option('--package-manager <pm>', 'Package manager to use (npm, yarn, bun)')
|
|
112
|
+
.action(async (projectDirectory, options) => {
|
|
35
113
|
try {
|
|
36
|
-
console.log(
|
|
37
|
-
console.log(
|
|
114
|
+
console.log('');
|
|
115
|
+
console.log(ASCII_ART);
|
|
116
|
+
console.log('\x1b[36m Create a new project with Laju Framework\x1b[0m');
|
|
117
|
+
console.log('');
|
|
118
|
+
|
|
119
|
+
let packageManager;
|
|
120
|
+
if (options.packageManager) {
|
|
121
|
+
packageManager = options.packageManager;
|
|
122
|
+
if (!['npm', 'yarn', 'bun'].includes(packageManager)) {
|
|
123
|
+
console.log('\x1b[1;31m✖\x1b[0m \x1b[1;91mError:\x1b[0m Invalid package manager. Use npm, yarn, or bun.');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
const availablePackageManagers = detectAvailablePackageManagers();
|
|
128
|
+
packageManager = await selectPackageManager(availablePackageManagers);
|
|
129
|
+
}
|
|
130
|
+
console.log('\x1b[36m Using ' + packageManager + '\x1b[0m');
|
|
131
|
+
console.log('');
|
|
132
|
+
|
|
38
133
|
// If no project name, ask user
|
|
39
134
|
if (!projectDirectory) {
|
|
135
|
+
console.log('\x1b[36m Project name:\x1b[0m');
|
|
40
136
|
const response = await prompts({
|
|
41
137
|
type: 'text',
|
|
42
138
|
name: 'projectDirectory',
|
|
43
|
-
message: '
|
|
139
|
+
message: ''
|
|
44
140
|
});
|
|
45
141
|
projectDirectory = response.projectDirectory;
|
|
46
142
|
}
|
|
47
143
|
|
|
48
144
|
if (!projectDirectory) {
|
|
49
|
-
console.log('Project name is required to continue.');
|
|
145
|
+
console.log('\x1b[1;31m✖\x1b[0m \x1b[1;91mError:\x1b[0m Project name is required to continue.');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Validate project name (npm package name rules)
|
|
150
|
+
const nameRegex = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
151
|
+
if (!nameRegex.test(projectDirectory)) {
|
|
152
|
+
console.log('\x1b[1;31m✖\x1b[0m \x1b[1;91mError:\x1b[0m Invalid project name. Use lowercase letters, numbers, and hyphens only.');
|
|
50
153
|
process.exit(1);
|
|
51
154
|
}
|
|
52
155
|
|
|
@@ -54,15 +157,17 @@ program
|
|
|
54
157
|
|
|
55
158
|
// Check if directory exists
|
|
56
159
|
if (fs.existsSync(targetPath)) {
|
|
57
|
-
console.log(
|
|
160
|
+
console.log('\x1b[1;31m✖\x1b[0m \x1b[1;91mError:\x1b[0m Directory \x1b[36m' + projectDirectory + '\x1b[0m already exists. Choose another name.');
|
|
58
161
|
process.exit(1);
|
|
59
162
|
}
|
|
60
163
|
|
|
61
|
-
console.log(
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log('\x1b[90m Creating project at \x1b[36m' + targetPath + '\x1b[0m');
|
|
166
|
+
console.log('');
|
|
62
167
|
|
|
63
168
|
// Clone template from GitHub
|
|
64
169
|
const emitter = degit('maulanashalihin/laju');
|
|
65
|
-
|
|
170
|
+
|
|
66
171
|
await emitter.clone(targetPath);
|
|
67
172
|
|
|
68
173
|
// Read package.json from template
|
|
@@ -72,6 +177,18 @@ program
|
|
|
72
177
|
// Update project name in package.json
|
|
73
178
|
packageJson.name = projectDirectory;
|
|
74
179
|
|
|
180
|
+
// Update scripts for Windows before writing package.json
|
|
181
|
+
if (process.platform === 'win32') {
|
|
182
|
+
packageJson.scripts = {
|
|
183
|
+
"dev": "cls && if exist dist rmdir /s /q dist && if exist build rmdir /s /q build && npx concurrently \"vite\" \"timeout /t 1 >nul && npx nodemon --config nodemon.json\"",
|
|
184
|
+
"build": "if exist build rmdir /s /q build && vite build && tsc && tsc-alias -p tsconfig.json && if not exist dist\\views mkdir dist\\views && xcopy /s /e /i resources\\views dist\\views",
|
|
185
|
+
"refresh": "if exist data rmdir /s /q data && npx knex migrate:latest",
|
|
186
|
+
"test:ui": "npx vitest --ui",
|
|
187
|
+
"test:run": "npx vitest run",
|
|
188
|
+
"test:coverage": "npx vitest run --coverage"
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
75
192
|
// Write back package.json
|
|
76
193
|
fs.writeFileSync(
|
|
77
194
|
packageJsonPath,
|
|
@@ -79,41 +196,45 @@ program
|
|
|
79
196
|
);
|
|
80
197
|
|
|
81
198
|
// Change directory and run setup commands
|
|
199
|
+
const originalDir = process.cwd();
|
|
82
200
|
process.chdir(targetPath);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
console.log('🎉 Project created successfully!');
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
console.log('\x1b[36m Installing dependencies...\x1b[0m');
|
|
204
|
+
console.log('');
|
|
205
|
+
execSync(getInstallCommand(packageManager), { stdio: 'inherit', timeout: 300000 });
|
|
206
|
+
console.log('\x1b[32m ✓ Dependencies installed\x1b[0m');
|
|
207
|
+
console.log('');
|
|
208
|
+
|
|
209
|
+
console.log('\x1b[36m Setting up environment...\x1b[0m');
|
|
210
|
+
execSync(process.platform === 'win32' ? 'copy .env.example .env' : 'cp .env.example .env', { stdio: 'inherit', timeout: 10000 });
|
|
211
|
+
console.log('\x1b[32m ✓ Environment configured\x1b[0m');
|
|
212
|
+
console.log('');
|
|
213
|
+
|
|
214
|
+
console.log('\x1b[36m Preparing database...\x1b[0m');
|
|
215
|
+
execSync('npx knex migrate:latest', { stdio: 'inherit', timeout: 60000 });
|
|
216
|
+
console.log('\x1b[32m ✓ Database ready\x1b[0m');
|
|
217
|
+
} finally {
|
|
218
|
+
process.chdir(originalDir);
|
|
219
|
+
}
|
|
220
|
+
|
|
105
221
|
console.log('');
|
|
106
|
-
console.log('
|
|
107
|
-
|
|
222
|
+
console.log('\x1b[1;36m ✓ Project created successfully!\x1b[0m');
|
|
108
223
|
console.log('');
|
|
109
|
-
console.log('
|
|
110
|
-
console.log('1. 📁 cd ' + projectDirectory);
|
|
111
|
-
console.log('2. 🔥 run "npm run dev" to start the development server.');
|
|
112
|
-
console.log('3. 💻 enjoy coding !!!.');
|
|
224
|
+
console.log('\x1b[90m Next steps:\x1b[0m');
|
|
113
225
|
console.log('');
|
|
114
|
-
|
|
226
|
+
console.log(' cd ' + projectDirectory);
|
|
227
|
+
console.log(' ' + getRunCommand(packageManager) + ' dev');
|
|
228
|
+
console.log('');
|
|
229
|
+
console.log('\x1b[90m Learn more: https://laju.dev\x1b[0m');
|
|
230
|
+
console.log('\x1b[90m Docs: https://github.com/maulanashalihin/laju/tree/main/docs\x1b[0m');
|
|
231
|
+
console.log('');
|
|
232
|
+
|
|
115
233
|
} catch (error) {
|
|
116
234
|
console.error('Error:', error.message);
|
|
235
|
+
if (process.env.DEBUG) {
|
|
236
|
+
console.error(error.stack);
|
|
237
|
+
}
|
|
117
238
|
process.exit(1);
|
|
118
239
|
}
|
|
119
240
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-laju-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "CLI tool to scaffold new Laju framework projects",
|
|
4
5
|
"keywords": [
|
|
5
6
|
"laju",
|
|
6
7
|
"svelte",
|
|
@@ -9,20 +10,33 @@
|
|
|
9
10
|
"sqlite",
|
|
10
11
|
"boilerplate",
|
|
11
12
|
"template",
|
|
12
|
-
"generator"
|
|
13
|
+
"generator",
|
|
14
|
+
"cli"
|
|
13
15
|
],
|
|
16
|
+
"author": "Maulana Shalihin <maulana@drip.id>",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"homepage": "https://laju.dev",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/arghoritma/create-laju-app.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/maulanashalihin/laju/issues"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20.0.0"
|
|
28
|
+
},
|
|
14
29
|
"bin": {
|
|
15
30
|
"create-laju-app": "./bin/cli.js"
|
|
16
31
|
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
17
37
|
"dependencies": {
|
|
18
|
-
"child_process": "^1.0.2",
|
|
19
38
|
"commander": "^11.0.0",
|
|
20
39
|
"degit": "^2.8.4",
|
|
21
40
|
"prompts": "^2.4.2"
|
|
22
|
-
},
|
|
23
|
-
"homepage": "https://laju.dev",
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/maulanashalihin/laju.git"
|
|
27
41
|
}
|
|
28
42
|
}
|