create-laju-app 1.0.14 → 1.1.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/README.md +149 -309
- package/bin/cli.js +237 -120
- package/package.json +28 -28
package/README.md
CHANGED
|
@@ -1,309 +1,149 @@
|
|
|
1
|
-
# Laju
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
```
|
|
157
|
-
|
|
158
|
-
### 2. Creating the Database Migration
|
|
159
|
-
|
|
160
|
-
Create a migration for the posts table:
|
|
161
|
-
|
|
162
|
-
```bash
|
|
163
|
-
npx knex migrate:make create_posts_table
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
In the generated migration file:
|
|
167
|
-
|
|
168
|
-
```typescript
|
|
169
|
-
import { Knex } from "knex";
|
|
170
|
-
|
|
171
|
-
export async function up(knex: Knex): Promise<void> {
|
|
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
|
-
```
|
|
191
|
-
|
|
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
|
-
```
|
|
222
|
-
|
|
223
|
-
2. Create `resources/views/posts/create.svelte`:
|
|
224
|
-
|
|
225
|
-
```svelte
|
|
226
|
-
<script>
|
|
227
|
-
import { router } from '@inertiajs/svelte';
|
|
228
|
-
|
|
229
|
-
let form = {
|
|
230
|
-
title: '',
|
|
231
|
-
content: ''
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
function handleSubmit() {
|
|
235
|
-
router.post('/posts', form);
|
|
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>
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
### 4. Testing Your Application
|
|
273
|
-
|
|
274
|
-
1. Start the development server:
|
|
275
|
-
```bash
|
|
276
|
-
npm run dev
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
2. Visit `http://localhost:5555/posts` in your browser
|
|
280
|
-
3. Try creating a new post using the form
|
|
281
|
-
4. View the list of posts on the index page
|
|
282
|
-
|
|
283
|
-
### Key Concepts
|
|
284
|
-
|
|
285
|
-
1. **Routing**: Routes are defined in `routes/web.ts` using the HyperExpress router
|
|
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
|
|
290
|
-
|
|
291
|
-
### Best Practices
|
|
292
|
-
|
|
293
|
-
1. **File Organization**
|
|
294
|
-
- Keep controllers in `app/controllers`
|
|
295
|
-
- Place Svelte components in `resources/views`
|
|
296
|
-
- Database migrations in `migrations`
|
|
297
|
-
|
|
298
|
-
2. **Code Structure**
|
|
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
|
|
302
|
-
|
|
303
|
-
3. **Database**
|
|
304
|
-
- Always use migrations for database changes
|
|
305
|
-
- Use the Query Builder for complex queries
|
|
306
|
-
- Include timestamps for tracking record changes
|
|
307
|
-
|
|
308
|
-
Need help with anything specific? Feel free to ask!
|
|
309
|
-
|
|
1
|
+
# Laju
|
|
2
|
+
|
|
3
|
+
⚡ **High-performance TypeScript web framework** - 11x faster than Express.js
|
|
4
|
+
|
|
5
|
+
Build modern full-stack applications with HyperExpress, Svelte 5, and Inertia.js.
|
|
6
|
+
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
[](https://github.com/maulanashalihin/laju)
|
|
11
|
+
|
|
12
|
+
## 🚀 Quick Start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Create new project
|
|
16
|
+
npx create-laju-app my-project
|
|
17
|
+
cd my-project
|
|
18
|
+
|
|
19
|
+
# Setup database
|
|
20
|
+
cp .env.example .env
|
|
21
|
+
npx knex migrate:latest
|
|
22
|
+
|
|
23
|
+
# Start development
|
|
24
|
+
npm run dev
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Visit `http://localhost:5555`
|
|
28
|
+
|
|
29
|
+
## ✨ Features
|
|
30
|
+
|
|
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)
|
|
35
|
+
|
|
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
|
|
41
|
+
|
|
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
|
|
48
|
+
|
|
49
|
+
## 📊 Performance
|
|
50
|
+
|
|
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 |
|
|
57
|
+
|
|
58
|
+
*Benchmark: Simple JSON response on same hardware*
|
|
59
|
+
|
|
60
|
+
## 📚 Documentation
|
|
61
|
+
|
|
62
|
+
**[Complete Documentation →](docs/README.md)**
|
|
63
|
+
|
|
64
|
+
Documentation is organized for progressive learning from beginner to advanced.
|
|
65
|
+
|
|
66
|
+
### Getting Started
|
|
67
|
+
- [Introduction](docs/01-INTRODUCTION.md) - Framework overview, quick start
|
|
68
|
+
- [Project Structure](docs/02-PROJECT-STRUCTURE.md) - Directory layout
|
|
69
|
+
- [Database](docs/03-DATABASE.md) - Knex.js + SQLite
|
|
70
|
+
|
|
71
|
+
### Core Features
|
|
72
|
+
- [Routing & Controllers](docs/04-ROUTING-CONTROLLERS.md) - Handle requests
|
|
73
|
+
- [Frontend (Svelte 5)](docs/05-FRONTEND-SVELTE.md) - Build reactive UI
|
|
74
|
+
- [Authentication](docs/06-AUTHENTICATION.md) - Sessions + OAuth
|
|
75
|
+
- [Middleware](docs/07-MIDDLEWARE.md) - Auth, rate limiting
|
|
76
|
+
- [Validation](docs/08-VALIDATION.md) - Input validation
|
|
77
|
+
- [Email](docs/09-EMAIL.md) - Send emails
|
|
78
|
+
|
|
79
|
+
### Advanced Features
|
|
80
|
+
- [Storage (S3)](docs/10-STORAGE.md) - File uploads
|
|
81
|
+
- [Caching](docs/11-CACHING.md) - Redis + Database cache
|
|
82
|
+
- [Background Jobs](docs/12-BACKGROUND-JOBS.md) - Cron jobs, Scheduling
|
|
83
|
+
- [CSRF Protection](docs/13-CSRF.md) - Security
|
|
84
|
+
- [Translation](docs/14-TRANSLATION.md) - Multi-language
|
|
85
|
+
|
|
86
|
+
### Production
|
|
87
|
+
- [Best Practices](docs/16-BEST-PRACTICES.md) - Code quality
|
|
88
|
+
- [Security Guide](docs/17-SECURITY.md) - Secure your app
|
|
89
|
+
- [Testing](docs/19-TESTING.md) - Unit + Integration tests
|
|
90
|
+
- [Deployment](docs/20-DEPLOYMENT.md) - Production setup
|
|
91
|
+
|
|
92
|
+
## Project Structure
|
|
93
|
+
|
|
94
|
+
```
|
|
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
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Commands
|
|
113
|
+
|
|
114
|
+
```bash
|
|
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
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Tech Stack
|
|
123
|
+
|
|
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 |
|
|
132
|
+
|
|
133
|
+
## Author
|
|
134
|
+
|
|
135
|
+
**Maulana Shalihin** - [maulana@drip.id](mailto:maulana@drip.id)
|
|
136
|
+
|
|
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
|
|
140
|
+
|
|
141
|
+
## Support
|
|
142
|
+
|
|
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)
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT License
|
package/bin/cli.js
CHANGED
|
@@ -1,121 +1,238 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { program } = require('commander');
|
|
4
|
-
const prompts = require('prompts');
|
|
5
|
-
const degit = require('degit');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const { execSync } = require('child_process');
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
console.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const prompts = require('prompts');
|
|
5
|
+
const degit = require('degit');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
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
|
+
`;
|
|
103
|
+
|
|
104
|
+
program
|
|
105
|
+
.name('create-laju-app')
|
|
106
|
+
.description('CLI to create a new project from template')
|
|
107
|
+
.version('1.0.0');
|
|
108
|
+
|
|
109
|
+
program
|
|
110
|
+
.argument('[project-directory]', 'Project directory name')
|
|
111
|
+
.option('--package-manager <pm>', 'Package manager to use (npm, yarn, bun)')
|
|
112
|
+
.action(async (projectDirectory, options) => {
|
|
113
|
+
try {
|
|
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
|
+
|
|
133
|
+
// If no project name, ask user
|
|
134
|
+
if (!projectDirectory) {
|
|
135
|
+
console.log('\x1b[36m Project name:\x1b[0m');
|
|
136
|
+
const response = await prompts({
|
|
137
|
+
type: 'text',
|
|
138
|
+
name: 'projectDirectory',
|
|
139
|
+
message: ''
|
|
140
|
+
});
|
|
141
|
+
projectDirectory = response.projectDirectory;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!projectDirectory) {
|
|
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.');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const targetPath = path.resolve(projectDirectory);
|
|
157
|
+
|
|
158
|
+
// Check if directory exists
|
|
159
|
+
if (fs.existsSync(targetPath)) {
|
|
160
|
+
console.log('\x1b[1;31m✖\x1b[0m \x1b[1;91mError:\x1b[0m Directory \x1b[36m' + projectDirectory + '\x1b[0m already exists. Choose another name.');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log('\x1b[90m Creating project at \x1b[36m' + targetPath + '\x1b[0m');
|
|
166
|
+
console.log('');
|
|
167
|
+
|
|
168
|
+
// Clone template from GitHub
|
|
169
|
+
const emitter = degit('maulanashalihin/laju');
|
|
170
|
+
|
|
171
|
+
await emitter.clone(targetPath);
|
|
172
|
+
|
|
173
|
+
// Read package.json from template
|
|
174
|
+
const packageJsonPath = path.join(targetPath, 'package.json');
|
|
175
|
+
const packageJson = require(packageJsonPath);
|
|
176
|
+
|
|
177
|
+
// Update project name in package.json
|
|
178
|
+
packageJson.name = projectDirectory;
|
|
179
|
+
|
|
180
|
+
// Update scripts for Windows before writing package.json
|
|
181
|
+
if (process.platform === 'win32') {
|
|
182
|
+
packageJson.scripts = {
|
|
183
|
+
"dev": "cls && npx concurrently \"vite\" \"npx nodemon\"",
|
|
184
|
+
"build": "if exist build rmdir /s /q build && vite build && tsc && xcopy /s /e /i dist build && xcopy /s /e /i public build"
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Write back package.json
|
|
189
|
+
fs.writeFileSync(
|
|
190
|
+
packageJsonPath,
|
|
191
|
+
JSON.stringify(packageJson, null, 2)
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// Change directory and run setup commands
|
|
195
|
+
const originalDir = process.cwd();
|
|
196
|
+
process.chdir(targetPath);
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
console.log('\x1b[36m Installing dependencies...\x1b[0m');
|
|
200
|
+
console.log('');
|
|
201
|
+
execSync(getInstallCommand(packageManager), { stdio: 'inherit', timeout: 300000 });
|
|
202
|
+
console.log('\x1b[32m ✓ Dependencies installed\x1b[0m');
|
|
203
|
+
console.log('');
|
|
204
|
+
|
|
205
|
+
console.log('\x1b[36m Setting up environment...\x1b[0m');
|
|
206
|
+
execSync(process.platform === 'win32' ? 'copy .env.example .env' : 'cp .env.example .env', { stdio: 'inherit', timeout: 10000 });
|
|
207
|
+
console.log('\x1b[32m ✓ Environment configured\x1b[0m');
|
|
208
|
+
console.log('');
|
|
209
|
+
|
|
210
|
+
console.log('\x1b[36m Preparing database...\x1b[0m');
|
|
211
|
+
execSync('npx knex migrate:latest', { stdio: 'inherit', timeout: 60000 });
|
|
212
|
+
console.log('\x1b[32m ✓ Database ready\x1b[0m');
|
|
213
|
+
} finally {
|
|
214
|
+
process.chdir(originalDir);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log('\x1b[1;36m ✓ Project created successfully!\x1b[0m');
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log('\x1b[90m Next steps:\x1b[0m');
|
|
221
|
+
console.log('');
|
|
222
|
+
console.log(' cd ' + projectDirectory);
|
|
223
|
+
console.log(' ' + getRunCommand(packageManager) + ' dev');
|
|
224
|
+
console.log('');
|
|
225
|
+
console.log('\x1b[90m Learn more: https://laju.dev\x1b[0m');
|
|
226
|
+
console.log('\x1b[90m Docs: https://github.com/maulanashalihin/laju/tree/main/docs\x1b[0m');
|
|
227
|
+
console.log('');
|
|
228
|
+
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('Error:', error.message);
|
|
231
|
+
if (process.env.DEBUG) {
|
|
232
|
+
console.error(error.stack);
|
|
233
|
+
}
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
121
238
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-laju-app",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"keywords": [
|
|
5
|
-
"laju",
|
|
6
|
-
"svelte",
|
|
7
|
-
"tailwindcss",
|
|
8
|
-
"hyper-express",
|
|
9
|
-
"sqlite",
|
|
10
|
-
"boilerplate",
|
|
11
|
-
"template",
|
|
12
|
-
"generator"
|
|
13
|
-
],
|
|
14
|
-
"bin": {
|
|
15
|
-
"create-laju-app": "./bin/cli.js"
|
|
16
|
-
},
|
|
17
|
-
"dependencies": {
|
|
18
|
-
"child_process": "^1.0.2",
|
|
19
|
-
"commander": "^11.0.0",
|
|
20
|
-
"degit": "^2.8.4",
|
|
21
|
-
"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
|
-
}
|
|
28
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "create-laju-app",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"laju",
|
|
6
|
+
"svelte",
|
|
7
|
+
"tailwindcss",
|
|
8
|
+
"hyper-express",
|
|
9
|
+
"sqlite",
|
|
10
|
+
"boilerplate",
|
|
11
|
+
"template",
|
|
12
|
+
"generator"
|
|
13
|
+
],
|
|
14
|
+
"bin": {
|
|
15
|
+
"create-laju-app": "./bin/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"child_process": "^1.0.2",
|
|
19
|
+
"commander": "^11.0.0",
|
|
20
|
+
"degit": "^2.8.4",
|
|
21
|
+
"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
|
+
}
|
|
28
|
+
}
|