wad-lab 1.2.0 → 1.2.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/bin/index.js +734 -126
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -41,114 +41,556 @@ const frontendOptions = [
|
|
|
41
41
|
{ name: 'vue-axios-interceptor (Vue 3 + Axios Interceptors + Toast Alerts)', value: 'vue-axios-interceptor' }
|
|
42
42
|
];
|
|
43
43
|
|
|
44
|
+
const categories = [
|
|
45
|
+
{
|
|
46
|
+
id: 'fullstack',
|
|
47
|
+
title: 'Fullstack Web Applications',
|
|
48
|
+
icon: '🌐',
|
|
49
|
+
desc: 'Integrated Spring Boot + MongoDB + Vue 3 apps'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'springboot',
|
|
53
|
+
title: 'Spring Boot Backend APIs',
|
|
54
|
+
icon: '🍃',
|
|
55
|
+
desc: 'REST APIs, MongoDB, Aggregations, Join & MockMvc'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'servlet',
|
|
59
|
+
title: 'Java Servlets & Tomcat 9',
|
|
60
|
+
icon: '☕',
|
|
61
|
+
desc: 'Classic Tomcat Servlets, JDBC, Sessions & Web.xml'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'vue-axios',
|
|
65
|
+
title: 'Vue 3 + Axios HTTP Variants',
|
|
66
|
+
icon: '⚡',
|
|
67
|
+
desc: 'All 10+ architectural combinations of Vue 3 with Axios'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'vue-core',
|
|
71
|
+
title: 'Vue 3 Core Portals & State',
|
|
72
|
+
icon: '🎨',
|
|
73
|
+
desc: 'Router, Pinia, Native Fetch & Complete UI Portals'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'games',
|
|
77
|
+
title: 'Interactive Games (JS & Vue)',
|
|
78
|
+
icon: '🎮',
|
|
79
|
+
desc: 'Logic, DOM & State Management Games'
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'html-css',
|
|
83
|
+
title: 'HTML5 & CSS3 Foundations',
|
|
84
|
+
icon: '📄',
|
|
85
|
+
desc: 'Form Validation, DOM Manipulation & 50 CSS Properties'
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
|
|
44
89
|
const templates = [
|
|
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
|
-
|
|
90
|
+
// Fullstack (4)
|
|
91
|
+
{
|
|
92
|
+
category: 'fullstack',
|
|
93
|
+
value: 'fullstack-springboot-vue',
|
|
94
|
+
name: 'fullstack-springboot-vue (Spring Boot 2-Model API + Selectable Vue 3 Frontend)'
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
category: 'fullstack',
|
|
98
|
+
value: 'vue-springboot-mongo-crud',
|
|
99
|
+
name: 'vue-springboot-mongo-crud (Spring Boot + MongoDB + Vue 3 Direct CRUD)'
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
category: 'fullstack',
|
|
103
|
+
value: 'vue-springboot-mongo-aggregation',
|
|
104
|
+
name: 'vue-springboot-mongo-aggregation (Spring Boot Aggregation + Vue 3 Dashboard)'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
category: 'fullstack',
|
|
108
|
+
value: 'vue-router-pinia-springboot',
|
|
109
|
+
name: 'vue-router-pinia-springboot (Fullstack Enterprise: Router + Pinia + Spring Boot)'
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// Spring Boot (9)
|
|
113
|
+
{
|
|
114
|
+
category: 'springboot',
|
|
115
|
+
value: 'springboot-mongo-crud',
|
|
116
|
+
name: 'springboot-mongo-crud (4-Layer REST API: Controller -> Service -> Repo -> Mongo)'
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
category: 'springboot',
|
|
120
|
+
value: 'springboot-mongo-aggregation',
|
|
121
|
+
name: 'springboot-mongo-aggregation (Aggregation Pipeline: $group, $sum, $avg, $sort)'
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
category: 'springboot',
|
|
125
|
+
value: 'springboot-mongo-aggregation-join',
|
|
126
|
+
name: 'springboot-mongo-aggregation-join (MongoDB $lookup JOIN, @Query, @Aggregation)'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
category: 'springboot',
|
|
130
|
+
value: 'springboot-mockmvc',
|
|
131
|
+
name: 'springboot-mockmvc (Controller Unit Testing with MockMvc)'
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
category: 'springboot',
|
|
135
|
+
value: 'springboot-mongo-aggregation-mockmvc',
|
|
136
|
+
name: 'springboot-mongo-aggregation-mockmvc (Full Testing Lab: MockMvc + Aggregation)'
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
category: 'springboot',
|
|
140
|
+
value: 'springboot-2model-1controller-api',
|
|
141
|
+
name: 'springboot-2model-1controller-api (2 Models Dept & Emp, 2 Repos, 1 Service, 1 Controller)'
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
category: 'springboot',
|
|
145
|
+
value: 'springboot-course-registration',
|
|
146
|
+
name: 'springboot-course-registration (Exercise 5 — Course Registration REST API)'
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
category: 'springboot',
|
|
150
|
+
value: 'springboot-hospital-api',
|
|
151
|
+
name: 'springboot-hospital-api (Hospital OPD Appointments & Doctors API)'
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
category: 'springboot',
|
|
155
|
+
value: 'springboot-library-api',
|
|
156
|
+
name: 'springboot-library-api (Library Catalog, Loans & Analytics API)'
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// Servlets (5)
|
|
160
|
+
{
|
|
161
|
+
category: 'servlet',
|
|
162
|
+
value: 'servlet-basic',
|
|
163
|
+
name: 'servlet-basic (Tomcat GET + POST Basic Servlet)'
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
category: 'servlet',
|
|
167
|
+
value: 'servlet-flow',
|
|
168
|
+
name: 'servlet-flow (HelloServlet Execution Flow with GET/POST Form)'
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
category: 'servlet',
|
|
172
|
+
value: 'servlet-jdbc',
|
|
173
|
+
name: 'servlet-jdbc (Tomcat + MySQL JDBC Database Connection)'
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
category: 'servlet',
|
|
177
|
+
value: 'servlet-session-tracking',
|
|
178
|
+
name: 'servlet-session-tracking (HttpSession, Cookies, Redirect & URL Rewriting)'
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
category: 'servlet',
|
|
182
|
+
value: 'servlet-voter-eligibility',
|
|
183
|
+
name: 'servlet-voter-eligibility (Voting Eligibility Check: Name & Age Validation)'
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// Vue + Axios (11)
|
|
187
|
+
{
|
|
188
|
+
category: 'vue-axios',
|
|
189
|
+
value: 'vue-axios-basic',
|
|
190
|
+
name: 'vue-axios-basic (Vue 3 + Axios Direct CRUD in Single View)'
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
category: 'vue-axios-props-emits',
|
|
194
|
+
value: 'vue-axios-props-emits',
|
|
195
|
+
name: 'vue-axios-props-emits (Vue 3 + Axios + Parent/Child Props & Emits)'
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
category: 'vue-axios-router',
|
|
199
|
+
value: 'vue-axios-router',
|
|
200
|
+
name: 'vue-axios-router (Vue 3 + Axios + Router Views Dashboard & Details)'
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
category: 'vue-axios-pinia',
|
|
204
|
+
value: 'vue-axios-pinia',
|
|
205
|
+
name: 'vue-axios-pinia (Vue 3 + Axios + Pinia Centralized State Store)'
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
category: 'vue-axios-router-props',
|
|
209
|
+
value: 'vue-axios-router-props',
|
|
210
|
+
name: 'vue-axios-router-props (Vue 3 + Axios + Router + Component Props/Emits)'
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
category: 'vue-axios-pinia-props',
|
|
214
|
+
value: 'vue-axios-pinia-props',
|
|
215
|
+
name: 'vue-axios-pinia-props (Vue 3 + Axios + Pinia Store + Props/Emits)'
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
category: 'vue-axios-router-pinia',
|
|
219
|
+
value: 'vue-axios-router-pinia',
|
|
220
|
+
name: 'vue-axios-router-pinia (Vue 3 + Axios + Router Views + Pinia Store)'
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
category: 'vue-axios-router-pinia-props',
|
|
224
|
+
value: 'vue-axios-router-pinia-props',
|
|
225
|
+
name: 'vue-axios-router-pinia-props (The Complete Stack: Router + Pinia + Props + Emits)'
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
category: 'vue-axios-router-children',
|
|
229
|
+
value: 'vue-axios-router-children',
|
|
230
|
+
name: 'vue-axios-router-children (Vue 3 + Axios + Router Nested Children Layouts)'
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
category: 'vue-axios-interceptor',
|
|
234
|
+
value: 'vue-axios-interceptor',
|
|
235
|
+
name: 'vue-axios-interceptor (Vue 3 + Axios Interceptors + Toast Notifications)'
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
category: 'vue-axios',
|
|
239
|
+
value: 'vue-axios',
|
|
240
|
+
name: 'vue-axios (Classic Vue 3 Axios Service Layer CRUD)'
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
// Vue Core & Portals (16)
|
|
244
|
+
{
|
|
245
|
+
category: 'vue-core',
|
|
246
|
+
value: 'vue-basics',
|
|
247
|
+
name: 'vue-basics (Core Reactivity: ref, reactive, computed, v-model, watch)'
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
category: 'vue-props-emits',
|
|
251
|
+
value: 'vue-props-emits',
|
|
252
|
+
name: 'vue-props-emits (Parent/Child Component Communication & Custom Events)'
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
category: 'vue-router',
|
|
256
|
+
value: 'vue-router',
|
|
257
|
+
name: 'vue-router (Vue Router Parent Layout & Nested Children)'
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
category: 'vue-pinia',
|
|
261
|
+
value: 'vue-pinia',
|
|
262
|
+
name: 'vue-pinia (Pinia State Store: State, Getters, Actions)'
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
category: 'vue-router-pinia',
|
|
266
|
+
value: 'vue-router-pinia',
|
|
267
|
+
name: 'vue-router-pinia (Router Multi-Page Views + Pinia Store)'
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
category: 'vue-router-pinia-props',
|
|
271
|
+
value: 'vue-router-pinia-props',
|
|
272
|
+
name: 'vue-router-pinia-props (Router + Pinia Store + Component Props/Emits)'
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
category: 'vue-fetch',
|
|
276
|
+
value: 'vue-fetch',
|
|
277
|
+
name: 'vue-fetch (Native Fetch API CRUD without Axios)'
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
category: 'vue-todo-pinia',
|
|
281
|
+
value: 'vue-todo-pinia',
|
|
282
|
+
name: 'vue-todo-pinia (Pinia Task & Todo Manager)'
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
category: 'vue-todo-router',
|
|
286
|
+
value: 'vue-todo-router',
|
|
287
|
+
name: 'vue-todo-router (Router Multi-View Task Application)'
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
category: 'vue-cart-router-pinia',
|
|
291
|
+
value: 'vue-cart-router-pinia',
|
|
292
|
+
name: 'vue-cart-router-pinia (E-Commerce Shopping Cart with Router & Pinia)'
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
category: 'vue-food-ordering',
|
|
296
|
+
value: 'vue-food-ordering',
|
|
297
|
+
name: 'vue-food-ordering (Exercise 7 — Online Food Ordering Application)'
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
category: 'vue-symposium-system',
|
|
301
|
+
value: 'vue-symposium-system',
|
|
302
|
+
name: 'vue-symposium-system (Exercise 6 — College Symposium Management System)'
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
category: 'vue-banking-app',
|
|
306
|
+
value: 'vue-banking-app',
|
|
307
|
+
name: 'vue-banking-app (Banking Portal & Transaction Ledger System)'
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
category: 'vue-employee-system',
|
|
311
|
+
value: 'vue-employee-system',
|
|
312
|
+
name: 'vue-employee-system (Employee HR, Payroll & Records System)'
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
category: 'vue-hospital-portal',
|
|
316
|
+
value: 'vue-hospital-portal',
|
|
317
|
+
name: 'vue-hospital-portal (Hospital OPD & Patient Vitals Portal)'
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
category: 'vue-library-system',
|
|
321
|
+
value: 'vue-library-system',
|
|
322
|
+
name: 'vue-library-system (Library Catalog & Book Circulation System)'
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
// Games (9)
|
|
326
|
+
{
|
|
327
|
+
category: 'games',
|
|
328
|
+
value: 'game-bingo',
|
|
329
|
+
name: 'game-bingo (Exercise 3 — Two-Player JavaScript Bingo Game)'
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
category: 'games',
|
|
333
|
+
value: 'game-rock-paper-scissors',
|
|
334
|
+
name: 'game-rock-paper-scissors (Player vs Computer Rock Paper Scissors — Vanilla JS)'
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
category: 'games',
|
|
338
|
+
value: 'game-tic-tac-toe',
|
|
339
|
+
name: 'game-tic-tac-toe (3x3 Turn Grid with Win Detection — Vanilla JS)'
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
category: 'games',
|
|
343
|
+
value: 'game-whack-a-mole',
|
|
344
|
+
name: 'game-whack-a-mole (3x3 Mole Whack with Timer & Score — Vanilla JS)'
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
category: 'games',
|
|
348
|
+
value: 'game-memory-cards',
|
|
349
|
+
name: 'game-memory-cards (3x4 Flashcard Matching Game — Vanilla JS)'
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
category: 'games',
|
|
353
|
+
value: 'vue-game-rps',
|
|
354
|
+
name: 'vue-game-rps (Rock Paper Scissors with Vue 3 + Pinia Scoreboard)'
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
category: 'games',
|
|
358
|
+
value: 'vue-game-tictactoe',
|
|
359
|
+
name: 'vue-game-tictactoe (Tic Tac Toe with Vue 3 + Pinia Move History)'
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
category: 'games',
|
|
363
|
+
value: 'vue-game-whackamole',
|
|
364
|
+
name: 'vue-game-whackamole (Whack-A-Mole with Vue 3 + Pinia Countdown)'
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
category: 'games',
|
|
368
|
+
value: 'vue-memory-game',
|
|
369
|
+
name: 'vue-memory-game (Card Matching Game with Vue 3 + Pinia Flips)'
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
// HTML / CSS (2)
|
|
373
|
+
{
|
|
374
|
+
category: 'html-css',
|
|
375
|
+
value: 'html-css-js',
|
|
376
|
+
name: 'html-css-js (Form Validation Regex, Required & Dynamic DOM)'
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
category: 'html-css',
|
|
380
|
+
value: 'html-css-styles',
|
|
381
|
+
name: 'html-css-styles (50 CSS Properties Showcase: Box Model, Flex, Grid, Animations)'
|
|
382
|
+
}
|
|
102
383
|
];
|
|
103
384
|
|
|
104
385
|
const PAGE_SIZE = 12;
|
|
386
|
+
const templatesRoot = path.join(__dirname, '..', 'templates');
|
|
387
|
+
|
|
388
|
+
let currentView = 'MAIN'; // 'MAIN' | 'CATEGORY' | 'SEARCH' | 'ALL'
|
|
389
|
+
let activeCategory = null;
|
|
390
|
+
let searchQuery = '';
|
|
105
391
|
let selectedIndex = 0;
|
|
106
392
|
const selectedValues = new Set();
|
|
107
|
-
const templatesRoot = path.join(__dirname, '..', 'templates');
|
|
108
393
|
let isPrompting = false;
|
|
109
394
|
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
395
|
+
function getActiveItemList() {
|
|
396
|
+
if (currentView === 'MAIN') {
|
|
397
|
+
const list = categories.map((cat, idx) => {
|
|
398
|
+
const count = templates.filter(t => t.category === cat.id).length;
|
|
399
|
+
return {
|
|
400
|
+
type: 'category',
|
|
401
|
+
id: cat.id,
|
|
402
|
+
name: `[${idx + 1}] ${cat.icon} ${cat.title} (${count} templates)`,
|
|
403
|
+
desc: cat.desc
|
|
404
|
+
};
|
|
405
|
+
});
|
|
406
|
+
list.push({ type: 'action', id: 'search', name: '[S] 🔍 Search templates by keyword...', desc: 'Quickly find templates by typing name or topic' });
|
|
407
|
+
list.push({ type: 'action', id: 'all_view', name: '[L] 📋 View all 56 templates (grouped)', desc: 'Browse the full catalog with section headers' });
|
|
408
|
+
list.push({ type: 'action', id: 'unpack_all', name: '[A] 📦 Unpack all 56 templates in 1-click', desc: 'Copy all 56 templates into target folder' });
|
|
409
|
+
return list;
|
|
122
410
|
}
|
|
123
|
-
const end = Math.min(start + PAGE_SIZE, total);
|
|
124
411
|
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
} else {
|
|
128
|
-
console.log(' --- Top of list ---');
|
|
412
|
+
if (currentView === 'CATEGORY') {
|
|
413
|
+
return templates.filter(t => t.category === activeCategory.id);
|
|
129
414
|
}
|
|
130
415
|
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
416
|
+
if (currentView === 'SEARCH') {
|
|
417
|
+
const q = searchQuery.toLowerCase().trim();
|
|
418
|
+
if (!q) return [];
|
|
419
|
+
return templates.filter(t =>
|
|
420
|
+
t.value.toLowerCase().includes(q) ||
|
|
421
|
+
t.name.toLowerCase().includes(q) ||
|
|
422
|
+
t.category.toLowerCase().includes(q)
|
|
423
|
+
);
|
|
138
424
|
}
|
|
139
425
|
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
426
|
+
if (currentView === 'ALL') {
|
|
427
|
+
return templates;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return [];
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function renderScreen() {
|
|
434
|
+
console.clear();
|
|
435
|
+
|
|
436
|
+
if (currentView === 'MAIN') {
|
|
437
|
+
console.log('========================================================');
|
|
438
|
+
console.log(' 🚀 WAD LAB STARTER TEMPLATES ');
|
|
439
|
+
console.log(' 56 Exam-Ready & Lab-Tested Templates ');
|
|
440
|
+
console.log('========================================================\n');
|
|
441
|
+
console.log('Navigate with [UP / DOWN] arrow keys');
|
|
442
|
+
console.log('Press [ENTER] or press [1-7] to enter a category');
|
|
443
|
+
console.log('Press [S] to Search, [A] to Unpack All, [Ctrl+C] to Exit\n');
|
|
444
|
+
|
|
445
|
+
const items = getActiveItemList();
|
|
446
|
+
items.forEach((item, idx) => {
|
|
447
|
+
if (item.id === 'search') {
|
|
448
|
+
console.log(' ----------------------------------------------------');
|
|
449
|
+
}
|
|
450
|
+
const isCursor = idx === selectedIndex;
|
|
451
|
+
const prefix = isCursor ? ' 👉 ' : ' ';
|
|
452
|
+
console.log(prefix + item.name);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
const activeItem = items[selectedIndex];
|
|
456
|
+
if (activeItem && activeItem.desc) {
|
|
457
|
+
console.log('\n ℹ️ ' + activeItem.desc);
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (currentView === 'CATEGORY') {
|
|
463
|
+
console.log('========================================================');
|
|
464
|
+
console.log(` ${activeCategory.icon} ${activeCategory.title}`);
|
|
465
|
+
console.log(` ${activeCategory.desc}`);
|
|
466
|
+
console.log('========================================================\n');
|
|
467
|
+
console.log(' [SPACE] Select / Deselect (multi-select supported)');
|
|
468
|
+
console.log(' [ENTER] Confirm selection and proceed');
|
|
469
|
+
console.log(' [B / ESC] Back to Categories Menu\n');
|
|
470
|
+
|
|
471
|
+
const items = getActiveItemList();
|
|
472
|
+
const total = items.length;
|
|
473
|
+
|
|
474
|
+
let start = selectedIndex - Math.floor(PAGE_SIZE / 2);
|
|
475
|
+
if (start < 0) start = 0;
|
|
476
|
+
if (start + PAGE_SIZE > total) {
|
|
477
|
+
start = Math.max(0, total - PAGE_SIZE);
|
|
478
|
+
}
|
|
479
|
+
const end = Math.min(start + PAGE_SIZE, total);
|
|
480
|
+
|
|
481
|
+
if (start > 0) {
|
|
482
|
+
console.log(` ▲ (${start} more above)`);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
for (let i = start; i < end; i++) {
|
|
486
|
+
const t = items[i];
|
|
487
|
+
const isCursor = i === selectedIndex;
|
|
488
|
+
const isChecked = selectedValues.has(t.value);
|
|
489
|
+
const box = isChecked ? '[✓]' : '[ ]';
|
|
490
|
+
const prefix = isCursor ? ' 👉 ' : ' ';
|
|
491
|
+
console.log(prefix + box + ' ' + t.name);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (end < total) {
|
|
495
|
+
console.log(` ▼ (${total - end} more below)`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const selCount = selectedValues.size;
|
|
499
|
+
console.log(`\n Selected: ${selCount} template(s) [${selectedIndex + 1}/${total}]`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (currentView === 'SEARCH') {
|
|
504
|
+
console.log('========================================================');
|
|
505
|
+
console.log(' 🔍 Instant Template Search');
|
|
506
|
+
console.log('========================================================');
|
|
507
|
+
console.log(` Query: "${searchQuery}" (type letters/numbers, Backspace to delete)`);
|
|
508
|
+
console.log(' [SPACE] Select | [ENTER] Confirm | [ESC] Back\n');
|
|
509
|
+
|
|
510
|
+
const items = getActiveItemList();
|
|
511
|
+
if (!searchQuery.trim()) {
|
|
512
|
+
console.log(' Start typing to search across all 56 templates...');
|
|
513
|
+
console.log(' Try keywords: "mongo", "join", "props", "servlet", "pinia", "game"\n');
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (items.length === 0) {
|
|
518
|
+
console.log(` No templates matched "${searchQuery}".`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const total = items.length;
|
|
523
|
+
let start = selectedIndex - Math.floor(PAGE_SIZE / 2);
|
|
524
|
+
if (start < 0) start = 0;
|
|
525
|
+
if (start + PAGE_SIZE > total) {
|
|
526
|
+
start = Math.max(0, total - PAGE_SIZE);
|
|
527
|
+
}
|
|
528
|
+
const end = Math.min(start + PAGE_SIZE, total);
|
|
529
|
+
|
|
530
|
+
if (start > 0) {
|
|
531
|
+
console.log(` ▲ (${start} more above)`);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
for (let i = start; i < end; i++) {
|
|
535
|
+
const t = items[i];
|
|
536
|
+
const isCursor = i === selectedIndex;
|
|
537
|
+
const isChecked = selectedValues.has(t.value);
|
|
538
|
+
const box = isChecked ? '[✓]' : '[ ]';
|
|
539
|
+
const prefix = isCursor ? ' 👉 ' : ' ';
|
|
540
|
+
console.log(prefix + box + ' ' + t.name);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (end < total) {
|
|
544
|
+
console.log(` ▼ (${total - end} more below)`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
console.log(`\n Matches: ${total} | Selected: ${selectedValues.size}`);
|
|
548
|
+
return;
|
|
144
549
|
}
|
|
145
550
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
551
|
+
if (currentView === 'ALL') {
|
|
552
|
+
console.log('========================================================');
|
|
553
|
+
console.log(' 📋 Full Catalog: All 56 Templates');
|
|
554
|
+
console.log('========================================================\n');
|
|
555
|
+
console.log(' [SPACE] Select / Deselect | [ENTER] Confirm | [ESC] Back\n');
|
|
556
|
+
|
|
557
|
+
const items = getActiveItemList();
|
|
558
|
+
const total = items.length;
|
|
559
|
+
|
|
560
|
+
let start = selectedIndex - Math.floor(PAGE_SIZE / 2);
|
|
561
|
+
if (start < 0) start = 0;
|
|
562
|
+
if (start + PAGE_SIZE > total) {
|
|
563
|
+
start = Math.max(0, total - PAGE_SIZE);
|
|
564
|
+
}
|
|
565
|
+
const end = Math.min(start + PAGE_SIZE, total);
|
|
566
|
+
|
|
567
|
+
if (start > 0) {
|
|
568
|
+
console.log(` ▲ (${start} more above)`);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
let lastCat = null;
|
|
572
|
+
for (let i = start; i < end; i++) {
|
|
573
|
+
const t = items[i];
|
|
574
|
+
if (t.category !== lastCat) {
|
|
575
|
+
const catObj = categories.find(c => c.id === t.category);
|
|
576
|
+
if (catObj) {
|
|
577
|
+
console.log(` --- ${catObj.icon} ${catObj.title} ---`);
|
|
578
|
+
}
|
|
579
|
+
lastCat = t.category;
|
|
580
|
+
}
|
|
581
|
+
const isCursor = i === selectedIndex;
|
|
582
|
+
const isChecked = selectedValues.has(t.value);
|
|
583
|
+
const box = isChecked ? '[✓]' : '[ ]';
|
|
584
|
+
const prefix = isCursor ? ' 👉 ' : ' ';
|
|
585
|
+
console.log(prefix + box + ' ' + t.name);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (end < total) {
|
|
589
|
+
console.log(` ▼ (${total - end} more below)`);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
console.log(`\n [${selectedIndex + 1}/${total}] Selected: ${selectedValues.size}`);
|
|
593
|
+
}
|
|
152
594
|
}
|
|
153
595
|
|
|
154
596
|
function promptFolderName(choices) {
|
|
@@ -208,7 +650,7 @@ function promptFolderName(choices) {
|
|
|
208
650
|
|
|
209
651
|
if (isAll) {
|
|
210
652
|
copyDirRecursive(templatesRoot, destDir);
|
|
211
|
-
console.log('\
|
|
653
|
+
console.log('\n✨ Successfully unpacked ALL 56 templates into: ' + destDir);
|
|
212
654
|
} else {
|
|
213
655
|
choices.forEach(c => {
|
|
214
656
|
if (c === 'fullstack-springboot-vue') {
|
|
@@ -238,29 +680,56 @@ function promptFolderName(choices) {
|
|
|
238
680
|
console.log('Unpacked [' + c + '] into: ' + dest);
|
|
239
681
|
}
|
|
240
682
|
});
|
|
241
|
-
console.log('\
|
|
683
|
+
console.log('\n✨ Successfully unpacked all chosen templates into: ' + destDir);
|
|
242
684
|
}
|
|
243
685
|
process.exit(0);
|
|
244
686
|
});
|
|
245
687
|
});
|
|
246
688
|
}
|
|
247
689
|
|
|
690
|
+
// Check command-line arguments for direct non-interactive use
|
|
248
691
|
if (process.argv[2]) {
|
|
249
|
-
const arg = process.argv[2];
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
692
|
+
const arg = process.argv[2].trim();
|
|
693
|
+
if (arg === 'all' || arg === '--all' || arg === '-a') {
|
|
694
|
+
promptFolderName(['all']);
|
|
695
|
+
} else if (arg === '--cat' && process.argv[3]) {
|
|
696
|
+
const catId = process.argv[3].trim().toLowerCase();
|
|
697
|
+
const foundCat = categories.find(c => c.id === catId);
|
|
698
|
+
if (foundCat) {
|
|
699
|
+
activeCategory = foundCat;
|
|
700
|
+
currentView = 'CATEGORY';
|
|
701
|
+
selectedIndex = 0;
|
|
702
|
+
startInteractive();
|
|
703
|
+
} else {
|
|
704
|
+
console.error(`Unknown category: "${catId}". Available: ${categories.map(c => c.id).join(', ')}`);
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
} else if (arg === '--search' && process.argv[3]) {
|
|
708
|
+
searchQuery = process.argv.slice(3).join(' ');
|
|
709
|
+
currentView = 'SEARCH';
|
|
710
|
+
selectedIndex = 0;
|
|
711
|
+
startInteractive();
|
|
253
712
|
} else {
|
|
254
|
-
|
|
255
|
-
|
|
713
|
+
const found = templates.find(t => t.value === arg);
|
|
714
|
+
if (found) {
|
|
715
|
+
promptFolderName([found.value]);
|
|
716
|
+
} else {
|
|
717
|
+
console.error('Unknown template: ' + arg);
|
|
718
|
+
console.error(`Available templates (56 total) or run without arguments for the interactive menu.`);
|
|
719
|
+
process.exit(1);
|
|
720
|
+
}
|
|
256
721
|
}
|
|
257
722
|
} else {
|
|
723
|
+
startInteractive();
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function startInteractive() {
|
|
258
727
|
readline.emitKeypressEvents(process.stdin);
|
|
259
728
|
if (process.stdin.isTTY) {
|
|
260
729
|
process.stdin.setRawMode(true);
|
|
261
730
|
}
|
|
262
731
|
|
|
263
|
-
|
|
732
|
+
renderScreen();
|
|
264
733
|
|
|
265
734
|
process.stdin.on('keypress', (str, key) => {
|
|
266
735
|
if (isPrompting) return;
|
|
@@ -269,36 +738,175 @@ if (process.argv[2]) {
|
|
|
269
738
|
process.exit(0);
|
|
270
739
|
}
|
|
271
740
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
741
|
+
const items = getActiveItemList();
|
|
742
|
+
|
|
743
|
+
// MAIN VIEW INPUT
|
|
744
|
+
if (currentView === 'MAIN') {
|
|
745
|
+
if (key.name === 'up') {
|
|
746
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
747
|
+
renderScreen();
|
|
748
|
+
} else if (key.name === 'down') {
|
|
749
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
750
|
+
renderScreen();
|
|
751
|
+
} else if (str === 's' || str === 'S') {
|
|
752
|
+
currentView = 'SEARCH';
|
|
753
|
+
searchQuery = '';
|
|
754
|
+
selectedIndex = 0;
|
|
755
|
+
renderScreen();
|
|
756
|
+
} else if (str === 'a' || str === 'A') {
|
|
757
|
+
promptFolderName(['all']);
|
|
758
|
+
} else if (str === 'l' || str === 'L') {
|
|
759
|
+
currentView = 'ALL';
|
|
760
|
+
selectedIndex = 0;
|
|
761
|
+
renderScreen();
|
|
762
|
+
} else if (str >= '1' && str <= '7') {
|
|
763
|
+
const num = parseInt(str, 10);
|
|
764
|
+
activeCategory = categories[num - 1];
|
|
765
|
+
currentView = 'CATEGORY';
|
|
766
|
+
selectedIndex = 0;
|
|
767
|
+
renderScreen();
|
|
768
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
769
|
+
const item = items[selectedIndex];
|
|
770
|
+
if (item.type === 'category') {
|
|
771
|
+
activeCategory = categories.find(c => c.id === item.id);
|
|
772
|
+
currentView = 'CATEGORY';
|
|
773
|
+
selectedIndex = 0;
|
|
774
|
+
renderScreen();
|
|
775
|
+
} else if (item.id === 'search') {
|
|
776
|
+
currentView = 'SEARCH';
|
|
777
|
+
searchQuery = '';
|
|
778
|
+
selectedIndex = 0;
|
|
779
|
+
renderScreen();
|
|
780
|
+
} else if (item.id === 'all_view') {
|
|
781
|
+
currentView = 'ALL';
|
|
782
|
+
selectedIndex = 0;
|
|
783
|
+
renderScreen();
|
|
784
|
+
} else if (item.id === 'unpack_all') {
|
|
785
|
+
promptFolderName(['all']);
|
|
286
786
|
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
787
|
+
}
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// SEARCH VIEW INPUT
|
|
792
|
+
if (currentView === 'SEARCH') {
|
|
793
|
+
if (key.name === 'escape') {
|
|
794
|
+
currentView = 'MAIN';
|
|
795
|
+
selectedIndex = 0;
|
|
796
|
+
renderScreen();
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (key.name === 'up') {
|
|
801
|
+
if (items.length > 0) {
|
|
802
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
803
|
+
renderScreen();
|
|
804
|
+
}
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (key.name === 'down') {
|
|
809
|
+
if (items.length > 0) {
|
|
810
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
811
|
+
renderScreen();
|
|
812
|
+
}
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (key.name === 'space') {
|
|
817
|
+
if (items.length > 0) {
|
|
818
|
+
const currentVal = items[selectedIndex].value;
|
|
819
|
+
if (selectedValues.has(currentVal)) {
|
|
820
|
+
selectedValues.delete(currentVal);
|
|
821
|
+
} else {
|
|
822
|
+
selectedValues.add(currentVal);
|
|
823
|
+
}
|
|
824
|
+
renderScreen();
|
|
825
|
+
}
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
830
|
+
let chosen = Array.from(selectedValues);
|
|
831
|
+
if (chosen.length === 0 && items.length > 0) {
|
|
832
|
+
chosen = [items[selectedIndex].value];
|
|
833
|
+
}
|
|
834
|
+
if (chosen.length > 0) {
|
|
835
|
+
promptFolderName(chosen);
|
|
836
|
+
}
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (key.name === 'backspace') {
|
|
841
|
+
if (searchQuery.length > 0) {
|
|
842
|
+
searchQuery = searchQuery.slice(0, -1);
|
|
843
|
+
selectedIndex = 0;
|
|
844
|
+
renderScreen();
|
|
291
845
|
} else {
|
|
292
|
-
|
|
846
|
+
currentView = 'MAIN';
|
|
847
|
+
selectedIndex = 0;
|
|
848
|
+
renderScreen();
|
|
849
|
+
}
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Typing search query
|
|
854
|
+
if (str && str.length === 1 && !key.ctrl && !key.meta) {
|
|
855
|
+
searchQuery += str;
|
|
856
|
+
selectedIndex = 0;
|
|
857
|
+
renderScreen();
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// CATEGORY / ALL VIEW INPUT
|
|
864
|
+
if (currentView === 'CATEGORY' || currentView === 'ALL') {
|
|
865
|
+
if (key.name === 'escape' || key.name === 'left' || str === 'b' || str === 'B') {
|
|
866
|
+
currentView = 'MAIN';
|
|
867
|
+
selectedIndex = 0;
|
|
868
|
+
renderScreen();
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (key.name === 'up') {
|
|
873
|
+
if (items.length > 0) {
|
|
874
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
875
|
+
renderScreen();
|
|
876
|
+
}
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (key.name === 'down') {
|
|
881
|
+
if (items.length > 0) {
|
|
882
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
883
|
+
renderScreen();
|
|
293
884
|
}
|
|
885
|
+
return;
|
|
294
886
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
887
|
+
|
|
888
|
+
if (key.name === 'space') {
|
|
889
|
+
if (items.length > 0) {
|
|
890
|
+
const currentVal = items[selectedIndex].value;
|
|
891
|
+
if (selectedValues.has(currentVal)) {
|
|
892
|
+
selectedValues.delete(currentVal);
|
|
893
|
+
} else {
|
|
894
|
+
selectedValues.add(currentVal);
|
|
895
|
+
}
|
|
896
|
+
renderScreen();
|
|
897
|
+
}
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
902
|
+
let chosen = Array.from(selectedValues);
|
|
903
|
+
if (chosen.length === 0 && items.length > 0) {
|
|
904
|
+
chosen = [items[selectedIndex].value];
|
|
905
|
+
}
|
|
906
|
+
if (chosen.length > 0) {
|
|
907
|
+
promptFolderName(chosen);
|
|
908
|
+
}
|
|
300
909
|
}
|
|
301
|
-
promptFolderName(chosen);
|
|
302
910
|
}
|
|
303
911
|
});
|
|
304
912
|
}
|