wad-lab 1.2.2 → 1.2.4
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 +477 -627
- package/package.json +1 -1
- package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/App.vue +241 -429
- package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/components/ItemFormModal.vue +183 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/components/ItemPatchModal.vue +193 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/components/ItemTable.vue +177 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/App.vue +150 -441
- package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/components/ItemFormModal.vue +207 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/components/ItemStats.vue +64 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/components/ItemTable.vue +195 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/App.vue +41 -90
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/components/ItemFormModal.vue +173 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/components/ItemTable.vue +102 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/components/NavBar.vue +57 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/views/DepartmentView.vue +132 -296
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/views/EmployeeView.vue +126 -219
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/components/ItemDetail.vue +115 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/components/ItemTable.vue +104 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/components/SideBar.vue +59 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/CompanyLayout.vue +2 -51
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/DepartmentListView.vue +92 -250
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/EmployeeDetailView.vue +59 -181
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/EmployeeListView.vue +67 -315
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/App.vue +19 -70
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/ItemFormModal.vue +173 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/ItemPatchModal.vue +144 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/ItemTable.vue +102 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/NavBar.vue +53 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/views/DepartmentView.vue +107 -229
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/views/EmployeeView.vue +143 -257
- package/templates/game-bingo/script.js +1 -1
- package/templates/game-memory-cards/script.js +2 -2
- package/templates/game-rock-paper-scissors/index.html +3 -3
- package/templates/game-rock-paper-scissors/script.js +2 -2
- package/templates/game-tic-tac-toe/script.js +2 -2
- package/templates/game-whack-a-mole/index.html +9 -9
- package/templates/servlet-basic/WEB-INF/ItemServlet.java +13 -0
- package/templates/servlet-basic/WEB-INF/classes/ItemServlet.class +0 -0
- package/templates/servlet-flow/WEB-INF/HelloServlet.java +34 -25
- package/templates/servlet-flow/WEB-INF/classes/HelloServlet.class +0 -0
- package/templates/servlet-jdbc/WEB-INF/JdbcItemServlet.java +13 -0
- package/templates/servlet-jdbc/WEB-INF/classes/JdbcItemServlet.class +0 -0
- package/templates/snippets/mongo-queries-cheatsheet.txt +105 -0
- package/templates/springboot-2model-aggregation-join/.mvn/wrapper/maven-wrapper.properties +3 -0
- package/templates/springboot-2model-aggregation-join/mvnw +295 -0
- package/templates/springboot-2model-aggregation-join/mvnw.cmd +189 -0
- package/templates/springboot-2model-aggregation-join/pom.xml +54 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/AggregationApplication.java +11 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/controller/DepartmentController.java +65 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/controller/EmployeeController.java +65 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/dto/DepartmentStatsDTO.java +15 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/dto/EmployeeJoinDTO.java +19 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/model/Department.java +20 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/model/Employee.java +21 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/repository/DepartmentRepository.java +9 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/repository/EmployeeRepository.java +9 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/service/DepartmentService.java +81 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/service/EmployeeService.java +80 -0
- package/templates/springboot-2model-aggregation-join/src/main/resources/application.properties +2 -0
- package/templates/springboot-2model-aggregation-join/src/test/java/com/example/aggregation/AggregationApplicationTests.java +13 -0
- package/templates/vue-2model-crud-patch/README.md +64 -0
- package/templates/vue-2model-crud-patch/index.html +12 -0
- package/templates/vue-2model-crud-patch/package-lock.json +1785 -0
- package/templates/vue-2model-crud-patch/package.json +21 -0
- package/templates/vue-2model-crud-patch/src/App.vue +63 -0
- package/templates/vue-2model-crud-patch/src/components/AnalyticsBoard.vue +291 -0
- package/templates/vue-2model-crud-patch/src/components/DepartmentFormModal.vue +138 -0
- package/templates/vue-2model-crud-patch/src/components/DepartmentPatchModal.vue +210 -0
- package/templates/vue-2model-crud-patch/src/components/DepartmentTable.vue +133 -0
- package/templates/vue-2model-crud-patch/src/components/EmployeeFormModal.vue +177 -0
- package/templates/vue-2model-crud-patch/src/components/EmployeePatchModal.vue +221 -0
- package/templates/vue-2model-crud-patch/src/components/EmployeeTable.vue +171 -0
- package/templates/vue-2model-crud-patch/src/components/NavBar.vue +156 -0
- package/templates/vue-2model-crud-patch/src/main.js +12 -0
- package/templates/vue-2model-crud-patch/src/router/index.js +33 -0
- package/templates/vue-2model-crud-patch/src/services/api.js +64 -0
- package/templates/vue-2model-crud-patch/src/stores/departmentStore.js +149 -0
- package/templates/vue-2model-crud-patch/src/stores/employeeStore.js +143 -0
- package/templates/vue-2model-crud-patch/src/style.css +415 -0
- package/templates/vue-2model-crud-patch/src/views/AnalyticsView.vue +59 -0
- package/templates/vue-2model-crud-patch/src/views/DepartmentsView.vue +135 -0
- package/templates/vue-2model-crud-patch/src/views/EmployeesView.vue +143 -0
- package/templates/vue-2model-crud-patch/vite.config.js +9 -0
- package/templates/vue-axios/src/App.vue +255 -264
- package/templates/vue-axios/src/components/ItemFormModal.vue +183 -0
- package/templates/vue-axios/src/components/ItemPatchModal.vue +193 -0
- package/templates/vue-axios/src/components/ItemTable.vue +177 -0
- package/templates/vue-axios-basic/src/App.vue +241 -429
- package/templates/vue-axios-basic/src/components/ItemFormModal.vue +183 -0
- package/templates/vue-axios-basic/src/components/ItemPatchModal.vue +193 -0
- package/templates/vue-axios-basic/src/components/ItemTable.vue +177 -0
- package/templates/vue-axios-pinia/src/App.vue +150 -441
- package/templates/vue-axios-pinia/src/components/ItemFormModal.vue +207 -0
- package/templates/vue-axios-pinia/src/components/ItemStats.vue +64 -0
- package/templates/vue-axios-pinia/src/components/ItemTable.vue +195 -0
- package/templates/vue-axios-router/src/App.vue +41 -90
- package/templates/vue-axios-router/src/components/ItemFormModal.vue +173 -0
- package/templates/vue-axios-router/src/components/ItemTable.vue +102 -0
- package/templates/vue-axios-router/src/components/NavBar.vue +57 -0
- package/templates/vue-axios-router/src/views/DepartmentView.vue +132 -296
- package/templates/vue-axios-router/src/views/EmployeeView.vue +126 -219
- package/templates/vue-axios-router-children/src/components/ItemDetail.vue +115 -0
- package/templates/vue-axios-router-children/src/components/ItemTable.vue +104 -0
- package/templates/vue-axios-router-children/src/components/SideBar.vue +59 -0
- package/templates/vue-axios-router-children/src/views/CompanyLayout.vue +2 -51
- package/templates/vue-axios-router-children/src/views/DepartmentListView.vue +92 -250
- package/templates/vue-axios-router-children/src/views/EmployeeDetailView.vue +59 -181
- package/templates/vue-axios-router-children/src/views/EmployeeListView.vue +67 -315
- package/templates/vue-axios-router-pinia/src/App.vue +19 -70
- package/templates/vue-axios-router-pinia/src/components/ItemFormModal.vue +173 -0
- package/templates/vue-axios-router-pinia/src/components/ItemPatchModal.vue +144 -0
- package/templates/vue-axios-router-pinia/src/components/ItemTable.vue +102 -0
- package/templates/vue-axios-router-pinia/src/components/NavBar.vue +53 -0
- package/templates/vue-axios-router-pinia/src/views/DepartmentView.vue +107 -229
- package/templates/vue-axios-router-pinia/src/views/EmployeeView.vue +143 -257
- package/templates/vue-banking-app/src/components/AccountOverview.vue +75 -0
- package/templates/vue-banking-app/src/components/TransactionForm.vue +89 -0
- package/templates/vue-banking-app/src/components/TransactionTable.vue +113 -0
- package/templates/vue-banking-app/src/stores/bankStore.js +16 -3
- package/templates/vue-banking-app/src/views/DashboardView.vue +23 -71
- package/templates/vue-banking-app/src/views/StatementView.vue +11 -158
- package/templates/vue-banking-app/src/views/TransactionFormView.vue +23 -121
- package/templates/vue-basics/src/App.vue +105 -76
- package/templates/vue-basics/src/components/CounterCard.vue +107 -0
- package/templates/vue-basics/src/components/ItemList.vue +99 -0
- package/templates/vue-basics/src/components/UserForm.vue +138 -0
- package/templates/vue-cart-router-pinia/src/components/CartSummary.vue +139 -0
- package/templates/vue-cart-router-pinia/src/components/CheckoutModal.vue +149 -0
- package/templates/vue-cart-router-pinia/src/components/ProductCard.vue +63 -0
- package/templates/vue-cart-router-pinia/src/stores/cartStore.js +11 -0
- package/templates/vue-cart-router-pinia/src/views/CartView.vue +52 -118
- package/templates/vue-cart-router-pinia/src/views/CheckoutView.vue +2 -5
- package/templates/vue-cart-router-pinia/src/views/ProductCatalogView.vue +20 -56
- package/templates/vue-employee-system/src/App.vue +75 -344
- package/templates/vue-employee-system/src/components/DepartmentFilter.vue +108 -0
- package/templates/vue-employee-system/src/components/EmployeeModal.vue +183 -0
- package/templates/vue-employee-system/src/components/EmployeeTable.vue +91 -0
- package/templates/vue-employee-system/src/stores/employeeStore.js +12 -0
- package/templates/vue-fetch/src/App.vue +261 -285
- package/templates/vue-fetch/src/components/FetchFormModal.vue +183 -0
- package/templates/vue-fetch/src/components/FetchPatchModal.vue +193 -0
- package/templates/vue-fetch/src/components/FetchTable.vue +177 -0
- package/templates/vue-food-ordering/src/components/CartSummary.vue +134 -0
- package/templates/vue-food-ordering/src/components/FoodMenu.vue +126 -0
- package/templates/vue-food-ordering/src/components/OrderModal.vue +150 -0
- package/templates/vue-food-ordering/src/views/MenuView.vue +11 -132
- package/templates/vue-food-ordering/src/views/OrderCartView.vue +31 -164
- package/templates/vue-game-rps/src/App.vue +29 -88
- package/templates/vue-game-tictactoe/src/App.vue +23 -53
- package/templates/vue-game-whackamole/src/App.vue +44 -63
- package/templates/vue-hospital-portal/src/components/AppointmentForm.vue +144 -0
- package/templates/vue-hospital-portal/src/components/DoctorCard.vue +73 -0
- package/templates/vue-hospital-portal/src/components/PatientTable.vue +123 -0
- package/templates/vue-hospital-portal/src/stores/hospitalStore.js +10 -1
- package/templates/vue-hospital-portal/src/views/OPDRegistrationView.vue +40 -416
- package/templates/vue-hospital-portal/src/views/PatientListView.vue +11 -301
- package/templates/vue-library-system/src/components/BookForm.vue +125 -0
- package/templates/vue-library-system/src/components/BookTable.vue +112 -0
- package/templates/vue-library-system/src/components/BorrowModal.vue +131 -0
- package/templates/vue-library-system/src/stores/libraryStore.js +26 -1
- package/templates/vue-library-system/src/views/BookCatalogView.vue +34 -415
- package/templates/vue-memory-game/src/App.vue +36 -40
- package/templates/vue-memory-game/src/stores/memoryStore.js +32 -37
- package/templates/vue-router/src/App.vue +24 -14
- package/templates/vue-router/src/components/AboutCard.vue +38 -0
- package/templates/vue-router/src/components/HomeHero.vue +75 -0
- package/templates/vue-router/src/components/NavBar.vue +54 -0
- package/templates/vue-router/src/views/ItemsLayout.vue +17 -9
- package/templates/vue-router/src/views/ListView.vue +106 -18
- package/templates/vue-router-pinia/src/App.vue +24 -14
- package/templates/vue-router-pinia/src/components/ItemFormModal.vue +163 -0
- package/templates/vue-router-pinia/src/components/ItemTable.vue +90 -0
- package/templates/vue-router-pinia/src/components/NavBar.vue +54 -0
- package/templates/vue-router-pinia/src/stores/itemStore.js +8 -1
- package/templates/vue-router-pinia/src/views/ListView.vue +78 -14
- package/templates/vue-router-pinia-springboot/frontend/src/App.vue +25 -8
- package/templates/vue-router-pinia-springboot/frontend/src/components/ItemFormModal.vue +183 -0
- package/templates/vue-router-pinia-springboot/frontend/src/components/ItemPatchModal.vue +193 -0
- package/templates/vue-router-pinia-springboot/frontend/src/components/ItemTable.vue +177 -0
- package/templates/vue-router-pinia-springboot/frontend/src/components/NavBar.vue +54 -0
- package/templates/vue-router-pinia-springboot/frontend/src/views/ListView.vue +92 -76
- package/templates/vue-springboot-mongo-aggregation/frontend/src/App.vue +184 -193
- package/templates/vue-springboot-mongo-aggregation/frontend/src/components/AnalyticsCards.vue +56 -0
- package/templates/vue-springboot-mongo-aggregation/frontend/src/components/CategorySummaryTable.vue +71 -0
- package/templates/vue-springboot-mongo-aggregation/frontend/src/components/ItemTable.vue +114 -0
- package/templates/vue-springboot-mongo-crud/frontend/src/App.vue +228 -158
- package/templates/vue-springboot-mongo-crud/frontend/src/components/ItemFormModal.vue +183 -0
- package/templates/vue-springboot-mongo-crud/frontend/src/components/ItemPatchModal.vue +193 -0
- package/templates/vue-springboot-mongo-crud/frontend/src/components/ItemTable.vue +177 -0
- package/templates/vue-symposium-system/src/App.vue +41 -256
- package/templates/vue-symposium-system/src/components/AttendeeTable.vue +100 -0
- package/templates/vue-symposium-system/src/components/EventCard.vue +67 -0
- package/templates/vue-symposium-system/src/components/RegistrationForm.vue +118 -0
- package/templates/vue-symposium-system/src/stores/symposiumStore.js +10 -0
- package/templates/vue-todo-pinia/src/App.vue +40 -138
- package/templates/vue-todo-pinia/src/components/TodoFilter.vue +74 -0
- package/templates/vue-todo-pinia/src/components/TodoInput.vue +53 -0
- package/templates/vue-todo-pinia/src/components/TodoList.vue +101 -0
- package/templates/vue-todo-pinia/src/stores/todoStore.js +6 -0
- package/templates/vue-todo-router/src/App.vue +5 -36
- package/templates/vue-todo-router/src/components/TodoForm.vue +78 -0
- package/templates/vue-todo-router/src/components/TodoList.vue +84 -0
- package/templates/vue-todo-router/src/components/TodoNavBar.vue +47 -0
- package/templates/vue-todo-router/src/data/todoState.js +7 -0
- package/templates/vue-todo-router/src/views/AddTodoView.vue +7 -66
- package/templates/vue-todo-router/src/views/TodoDetailView.vue +15 -1
- package/templates/vue-todo-router/src/views/TodoListView.vue +22 -64
package/bin/index.js
CHANGED
|
@@ -4,457 +4,407 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const readline = require('readline');
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// WAD LAB TEMPLATES CLI - STRICT ASCII (ZERO EMOJIS)
|
|
9
|
+
// ============================================================================
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
18
|
-
for (const entry of entries) {
|
|
19
|
-
const srcPath = path.join(src, entry.name);
|
|
20
|
-
const destPath = path.join(dest, entry.name);
|
|
21
|
-
|
|
22
|
-
if (entry.isDirectory()) {
|
|
23
|
-
copyDirRecursive(srcPath, destPath);
|
|
24
|
-
} else {
|
|
25
|
-
fs.copyFileSync(srcPath, destPath);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const frontendOptions = [
|
|
31
|
-
{ name: 'all (Include all 10 Vue variants inside frontend/)', value: 'all' },
|
|
32
|
-
{ name: 'vue-axios-basic (Vue 3 + Axios Direct CRUD)', value: 'vue-axios-basic' },
|
|
33
|
-
{ name: 'vue-axios-props-emits (Vue 3 + Axios + Props & Emits)', value: 'vue-axios-props-emits' },
|
|
34
|
-
{ name: 'vue-axios-router (Vue 3 + Axios + Router Views)', value: 'vue-axios-router' },
|
|
35
|
-
{ name: 'vue-axios-pinia (Vue 3 + Axios + Pinia Centralized Store)', value: 'vue-axios-pinia' },
|
|
36
|
-
{ name: 'vue-axios-router-props (Vue 3 + Axios + Router + Props/Emits)', value: 'vue-axios-router-props' },
|
|
37
|
-
{ name: 'vue-axios-pinia-props (Vue 3 + Axios + Pinia + Props/Emits)', value: 'vue-axios-pinia-props' },
|
|
38
|
-
{ name: 'vue-axios-router-pinia (Vue 3 + Axios + Router + Pinia)', value: 'vue-axios-router-pinia' },
|
|
39
|
-
{ name: 'vue-axios-router-pinia-props (The Kitchen Sink — Router + Pinia + Props + Emits)', value: 'vue-axios-router-pinia-props' },
|
|
40
|
-
{ name: 'vue-axios-router-children (Vue 3 + Axios + Router Nested Children)', value: 'vue-axios-router-children' },
|
|
41
|
-
{ name: 'vue-axios-interceptor (Vue 3 + Axios Interceptors + Toast Alerts)', value: 'vue-axios-interceptor' }
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
const featuredTemplates = [
|
|
11
|
+
const categories = [
|
|
45
12
|
{
|
|
13
|
+
id: 'servlet',
|
|
46
14
|
key: '1',
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
desc: 'Complete stack: 2-model Spring Boot backend + matching Vue frontend (Router/Pinia/Props)'
|
|
15
|
+
title: 'Java Servlets & Tomcat 9',
|
|
16
|
+
desc: '5 Classic Tomcat Servlets, JDBC, Sessions, Voter Eligibility & script.sh'
|
|
50
17
|
},
|
|
51
18
|
{
|
|
19
|
+
id: 'springboot',
|
|
52
20
|
key: '2',
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
desc: 'Extensible 2-model Spring Boot architecture (Department + Employee) with full CRUD & PATCH'
|
|
21
|
+
title: 'Spring Boot Backend APIs',
|
|
22
|
+
desc: '10 Pure-Lombok REST APIs: CRUD, Aggregations, Join, 2-Model & MockMvc'
|
|
56
23
|
},
|
|
57
24
|
{
|
|
25
|
+
id: 'vue',
|
|
58
26
|
key: '3',
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
desc: 'MongoDB $lookup multi-collection JOIN, pipeline stages ($match/$group), and @Query filters'
|
|
27
|
+
title: 'Frontend (Vue 3)',
|
|
28
|
+
desc: '28 Modular Vue 3 Frontends: 2-Model CRUD+PATCH, Axios, Router & Pinia'
|
|
62
29
|
},
|
|
63
30
|
{
|
|
31
|
+
id: 'html-css',
|
|
64
32
|
key: '4',
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
desc: 'Matching frontend for 2-model backend: Vue Router + Pinia centralized store + Props/Emits'
|
|
33
|
+
title: 'HTML5 & CSS3 Foundations',
|
|
34
|
+
desc: 'Form Validation, DOM Manipulation & 50 CSS Properties'
|
|
68
35
|
},
|
|
69
36
|
{
|
|
37
|
+
id: 'games',
|
|
70
38
|
key: '5',
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
desc: 'Matching frontend for 2-model backend: Vue Router pages + reusable components with Props/Emits'
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
key: '6',
|
|
77
|
-
value: 'servlet-jdbc',
|
|
78
|
-
name: '[6] servlet-jdbc (Tomcat 9 + MySQL Database Connection via JDBC)',
|
|
79
|
-
desc: 'Tomcat Servlet with MySQL Driver, JDBC connection, SQL statement execution, and HTML table'
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
key: '7',
|
|
83
|
-
value: 'servlet-flow',
|
|
84
|
-
name: '[7] servlet-flow (Tomcat 9 Execution Flow with GET/POST Form — No DB)',
|
|
85
|
-
desc: 'Pure servlet without DB: HTML form submission, doGet / doPost processing, and response render'
|
|
39
|
+
title: 'Interactive Browser Games',
|
|
40
|
+
desc: '9 Web Games: Bingo, Tic-Tac-Toe, RPS, Whack-a-Mole, Memory'
|
|
86
41
|
}
|
|
87
42
|
];
|
|
88
43
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
id: 'fullstack',
|
|
92
|
-
title: 'Fullstack Web Applications',
|
|
93
|
-
desc: 'Integrated Spring Boot + MongoDB + Vue 3 apps'
|
|
94
|
-
},
|
|
44
|
+
const templates = [
|
|
45
|
+
// 1. Java Servlets (5)
|
|
95
46
|
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
47
|
+
category: 'servlet',
|
|
48
|
+
value: 'servlet-basic',
|
|
49
|
+
name: 'servlet-basic (Basic GET/POST Servlet + Form)',
|
|
50
|
+
desc: 'Classic doGet / doPost request processing with HTML form submission and Tomcat script.sh'
|
|
99
51
|
},
|
|
100
52
|
{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
53
|
+
category: 'servlet',
|
|
54
|
+
value: 'servlet-flow',
|
|
55
|
+
name: 'servlet-flow (RequestDispatcher Forward & Include Flow - No DB)',
|
|
56
|
+
desc: 'Request dispatching between servlets, forward/include execution flow, and Tomcat script.sh'
|
|
104
57
|
},
|
|
105
58
|
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
59
|
+
category: 'servlet',
|
|
60
|
+
value: 'servlet-jdbc',
|
|
61
|
+
name: 'servlet-jdbc (Tomcat 9 + MySQL JDBC Connection & CRUD)',
|
|
62
|
+
desc: 'Servlet with MySQL Driver, JDBC connection, SQL statement execution, HTML table and script.sh'
|
|
109
63
|
},
|
|
110
64
|
{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
65
|
+
category: 'servlet',
|
|
66
|
+
value: 'servlet-session-tracking',
|
|
67
|
+
name: 'servlet-session-tracking (HttpSession, Cookies, URL Rewriting, Hidden Fields)',
|
|
68
|
+
desc: 'All 4 session tracking mechanisms in Java Servlets with Tomcat script.sh'
|
|
114
69
|
},
|
|
115
70
|
{
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
71
|
+
category: 'servlet',
|
|
72
|
+
value: 'servlet-voter-eligibility',
|
|
73
|
+
name: 'servlet-voter-eligibility (Form Validation & Age Check Logic)',
|
|
74
|
+
desc: 'Server-side input validation, voter eligibility verification, and Tomcat script.sh'
|
|
119
75
|
},
|
|
120
|
-
{
|
|
121
|
-
id: 'html-css',
|
|
122
|
-
title: 'HTML5 & CSS3 Foundations',
|
|
123
|
-
desc: 'Form Validation, DOM Manipulation & 50 CSS Properties'
|
|
124
|
-
}
|
|
125
|
-
];
|
|
126
76
|
|
|
127
|
-
|
|
128
|
-
// Fullstack (4)
|
|
129
|
-
{
|
|
130
|
-
category: 'fullstack',
|
|
131
|
-
value: 'fullstack-springboot-vue',
|
|
132
|
-
name: 'fullstack-springboot-vue (Spring Boot 2-Model API + Selectable Vue 3 Frontend)'
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
category: 'fullstack',
|
|
136
|
-
value: 'vue-springboot-mongo-crud',
|
|
137
|
-
name: 'vue-springboot-mongo-crud (Spring Boot + MongoDB + Vue 3 Direct CRUD)'
|
|
138
|
-
},
|
|
77
|
+
// 2. Spring Boot Backends (10)
|
|
139
78
|
{
|
|
140
|
-
category: '
|
|
141
|
-
value: '
|
|
142
|
-
name: '
|
|
79
|
+
category: 'springboot',
|
|
80
|
+
value: 'springboot-2model-aggregation-join',
|
|
81
|
+
name: 'springboot-2model-aggregation-join (Department + Employee, CRUD + PATCH, $lookup Join, $group Stats)',
|
|
82
|
+
desc: 'Pure Lombok (@Data, @NoArgsConstructor, @AllArgsConstructor), @Autowired injection, CRUD+PATCH, $lookup join & stats'
|
|
143
83
|
},
|
|
144
84
|
{
|
|
145
|
-
category: '
|
|
146
|
-
value: '
|
|
147
|
-
name: '
|
|
85
|
+
category: 'springboot',
|
|
86
|
+
value: 'springboot-2model-1controller-api',
|
|
87
|
+
name: 'springboot-2model-1controller-api (Department + Employee REST API, 2 Repos, 1 Controller)',
|
|
88
|
+
desc: 'Spring Boot 2-model REST API with Department and Employee endpoints in a unified controller'
|
|
148
89
|
},
|
|
149
|
-
|
|
150
|
-
// Spring Boot (9)
|
|
151
90
|
{
|
|
152
91
|
category: 'springboot',
|
|
153
|
-
value: 'springboot-mongo-
|
|
154
|
-
name: 'springboot-mongo-
|
|
92
|
+
value: 'springboot-mongo-aggregation-join',
|
|
93
|
+
name: 'springboot-mongo-aggregation-join (Customer + Order, $lookup Join Pipeline)',
|
|
94
|
+
desc: 'Order and Customer MongoDB aggregation join using $lookup, $unwind, and projected DTO'
|
|
155
95
|
},
|
|
156
96
|
{
|
|
157
97
|
category: 'springboot',
|
|
158
98
|
value: 'springboot-mongo-aggregation',
|
|
159
|
-
name: 'springboot-mongo-aggregation (
|
|
99
|
+
name: 'springboot-mongo-aggregation (Standalone Pipeline: $group, $sum, $avg, $sort)',
|
|
100
|
+
desc: 'Single collection aggregation analytics with MongoTemplate and aggregation stages'
|
|
160
101
|
},
|
|
161
102
|
{
|
|
162
103
|
category: 'springboot',
|
|
163
|
-
value: 'springboot-mongo-
|
|
164
|
-
name: 'springboot-mongo-
|
|
104
|
+
value: 'springboot-mongo-crud',
|
|
105
|
+
name: 'springboot-mongo-crud (4-Layer REST API: Controller -> Service -> Repo -> Mongo)',
|
|
106
|
+
desc: 'Standard MongoRepository CRUD starter with clean layers and full HTTP methods'
|
|
165
107
|
},
|
|
166
108
|
{
|
|
167
109
|
category: 'springboot',
|
|
168
110
|
value: 'springboot-mockmvc',
|
|
169
|
-
name: 'springboot-mockmvc (Controller
|
|
111
|
+
name: 'springboot-mockmvc (Controller Tests with MockMvc, Mockito & AssertJ)',
|
|
112
|
+
desc: 'Unit and integration testing for Spring Boot REST endpoints using MockMvc'
|
|
170
113
|
},
|
|
171
114
|
{
|
|
172
115
|
category: 'springboot',
|
|
173
116
|
value: 'springboot-mongo-aggregation-mockmvc',
|
|
174
|
-
name: 'springboot-mongo-aggregation-mockmvc (
|
|
175
|
-
|
|
176
|
-
{
|
|
177
|
-
category: 'springboot',
|
|
178
|
-
value: 'springboot-2model-1controller-api',
|
|
179
|
-
name: 'springboot-2model-1controller-api (2 Models Dept & Emp, 2 Repos, 1 Service, 1 Controller)'
|
|
117
|
+
name: 'springboot-mongo-aggregation-mockmvc (Aggregation Controller Covered by MockMvc Tests)',
|
|
118
|
+
desc: 'MongoDB aggregation API with complete test suite covering pipelines and projections'
|
|
180
119
|
},
|
|
181
120
|
{
|
|
182
121
|
category: 'springboot',
|
|
183
122
|
value: 'springboot-course-registration',
|
|
184
|
-
name: 'springboot-course-registration (
|
|
123
|
+
name: 'springboot-course-registration (Student & Course Enrollment REST API)',
|
|
124
|
+
desc: 'Domain REST API for university student registration, courses, and enrollments'
|
|
185
125
|
},
|
|
186
126
|
{
|
|
187
127
|
category: 'springboot',
|
|
188
128
|
value: 'springboot-hospital-api',
|
|
189
|
-
name: 'springboot-hospital-api (
|
|
129
|
+
name: 'springboot-hospital-api (Doctor & Patient Appointment Scheduling API)',
|
|
130
|
+
desc: 'Domain REST API for hospital appointment booking and patient record management'
|
|
190
131
|
},
|
|
191
132
|
{
|
|
192
133
|
category: 'springboot',
|
|
193
134
|
value: 'springboot-library-api',
|
|
194
|
-
name: 'springboot-library-api (
|
|
135
|
+
name: 'springboot-library-api (Book Catalog & Borrowing Transaction API)',
|
|
136
|
+
desc: 'Domain REST API for book lending, borrowing tracking, and member management'
|
|
195
137
|
},
|
|
196
138
|
|
|
197
|
-
//
|
|
139
|
+
// 3. Frontend (Vue 3) (28)
|
|
198
140
|
{
|
|
199
|
-
category: '
|
|
200
|
-
value: '
|
|
201
|
-
name: '
|
|
141
|
+
category: 'vue',
|
|
142
|
+
value: 'vue-2model-crud-patch',
|
|
143
|
+
name: 'vue-2model-crud-patch (2-Model Modular UI: Department & Employee, CRUD + PATCH + Join)',
|
|
144
|
+
desc: 'Vite + Vue 3 + Pinia + Router: Modular components (Tables, FormModals, PatchModals, AnalyticsBoard)'
|
|
202
145
|
},
|
|
203
146
|
{
|
|
204
|
-
category: '
|
|
205
|
-
value: '
|
|
206
|
-
name: '
|
|
147
|
+
category: 'vue',
|
|
148
|
+
value: 'vue-axios-router-pinia-props',
|
|
149
|
+
name: 'vue-axios-router-pinia-props (Full Enterprise: Router + Pinia + Axios + Props/Emits)',
|
|
150
|
+
desc: 'Complete standard stack with multi-view routes, Pinia store, and modular child components'
|
|
207
151
|
},
|
|
208
152
|
{
|
|
209
|
-
category: '
|
|
210
|
-
value: '
|
|
211
|
-
name: '
|
|
153
|
+
category: 'vue',
|
|
154
|
+
value: 'vue-axios-router-props',
|
|
155
|
+
name: 'vue-axios-router-props (Router Navigation + Modular Child Components with Props/Emits)',
|
|
156
|
+
desc: 'Multi-page routing passing state down via props and events up via emits'
|
|
212
157
|
},
|
|
213
158
|
{
|
|
214
|
-
category: '
|
|
215
|
-
value: '
|
|
216
|
-
name: '
|
|
159
|
+
category: 'vue',
|
|
160
|
+
value: 'vue-axios-pinia-props',
|
|
161
|
+
name: 'vue-axios-pinia-props (Pinia Store Passed Down to Child Components via Props)',
|
|
162
|
+
desc: 'Central store feeding child component sections with props and actions'
|
|
217
163
|
},
|
|
218
164
|
{
|
|
219
|
-
category: '
|
|
220
|
-
value: '
|
|
221
|
-
name: '
|
|
165
|
+
category: 'vue',
|
|
166
|
+
value: 'vue-axios-props-emits',
|
|
167
|
+
name: 'vue-axios-props-emits (Component Hierarchy: Form & List with Props Down and Emits Up)',
|
|
168
|
+
desc: 'Clean component isolation communicating purely via props and emit events'
|
|
222
169
|
},
|
|
223
|
-
|
|
224
|
-
// Vue + Axios (11)
|
|
225
170
|
{
|
|
226
|
-
category: 'vue
|
|
171
|
+
category: 'vue',
|
|
227
172
|
value: 'vue-axios-basic',
|
|
228
|
-
name: 'vue-axios-basic (
|
|
173
|
+
name: 'vue-axios-basic (Direct Axios CRUD with Modular Table, Form & Patch Modals)',
|
|
174
|
+
desc: 'Modular Vue 3 component CRUD with Axios HTTP client and partial PATCH modal'
|
|
229
175
|
},
|
|
230
176
|
{
|
|
231
|
-
category: 'vue
|
|
232
|
-
value: 'vue-axios
|
|
233
|
-
name: 'vue-axios
|
|
177
|
+
category: 'vue',
|
|
178
|
+
value: 'vue-axios',
|
|
179
|
+
name: 'vue-axios (Service-Layer Axios CRUD with Modular Components & PATCH)',
|
|
180
|
+
desc: 'ItemService abstraction layer with modular table and patch dialog'
|
|
234
181
|
},
|
|
235
182
|
{
|
|
236
|
-
category: 'vue
|
|
237
|
-
value: 'vue-axios-
|
|
238
|
-
name: 'vue-axios-
|
|
183
|
+
category: 'vue',
|
|
184
|
+
value: 'vue-axios-interceptor',
|
|
185
|
+
name: 'vue-axios-interceptor (Axios Request/Response Interceptors + Toast Notifications)',
|
|
186
|
+
desc: 'Global HTTP error catching, loading spinners, and toast alerts'
|
|
239
187
|
},
|
|
240
188
|
{
|
|
241
|
-
category: 'vue
|
|
189
|
+
category: 'vue',
|
|
242
190
|
value: 'vue-axios-pinia',
|
|
243
|
-
name: 'vue-axios-pinia (
|
|
244
|
-
|
|
245
|
-
{
|
|
246
|
-
category: 'vue-axios-router-props',
|
|
247
|
-
value: 'vue-axios-router-props',
|
|
248
|
-
name: 'vue-axios-router-props (Vue 3 + Axios + Router + Component Props/Emits)'
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
category: 'vue-axios-pinia-props',
|
|
252
|
-
value: 'vue-axios-pinia-props',
|
|
253
|
-
name: 'vue-axios-pinia-props (Vue 3 + Axios + Pinia Store + Props/Emits)'
|
|
191
|
+
name: 'vue-axios-pinia (Pinia Store with Axios HTTP Actions & Stats)',
|
|
192
|
+
desc: 'Centralized state store managing async CRUD requests with stats component'
|
|
254
193
|
},
|
|
255
194
|
{
|
|
256
|
-
category: 'vue
|
|
257
|
-
value: 'vue-axios-router
|
|
258
|
-
name: 'vue-axios-router
|
|
259
|
-
|
|
260
|
-
{
|
|
261
|
-
category: 'vue-axios-router-pinia-props',
|
|
262
|
-
value: 'vue-axios-router-pinia-props',
|
|
263
|
-
name: 'vue-axios-router-pinia-props (The Complete Stack: Router + Pinia + Props + Emits)'
|
|
195
|
+
category: 'vue',
|
|
196
|
+
value: 'vue-axios-router',
|
|
197
|
+
name: 'vue-axios-router (Vue Router Multi-View Navigation + Axios API)',
|
|
198
|
+
desc: 'Page views with navigation bar and modular table/form components'
|
|
264
199
|
},
|
|
265
200
|
{
|
|
266
|
-
category: 'vue
|
|
201
|
+
category: 'vue',
|
|
267
202
|
value: 'vue-axios-router-children',
|
|
268
|
-
name: 'vue-axios-router-children (
|
|
203
|
+
name: 'vue-axios-router-children (Nested Routes: Sidebar + Child Detail Views)',
|
|
204
|
+
desc: 'Nested <router-view> routes with sidebar navigation and detail components'
|
|
269
205
|
},
|
|
270
206
|
{
|
|
271
|
-
category: 'vue
|
|
272
|
-
value: 'vue-axios-
|
|
273
|
-
name: 'vue-axios-
|
|
274
|
-
|
|
275
|
-
{
|
|
276
|
-
category: 'vue-axios',
|
|
277
|
-
value: 'vue-axios',
|
|
278
|
-
name: 'vue-axios (Classic Vue 3 Axios Service Layer CRUD)'
|
|
207
|
+
category: 'vue',
|
|
208
|
+
value: 'vue-axios-router-pinia',
|
|
209
|
+
name: 'vue-axios-router-pinia (Standard SPA: Router + Pinia + Axios Service + PATCH)',
|
|
210
|
+
desc: 'Full SPA architecture with modular tables, modals, and patch actions'
|
|
279
211
|
},
|
|
280
|
-
|
|
281
|
-
// Vue Core & Portals (16)
|
|
282
212
|
{
|
|
283
|
-
category: 'vue
|
|
213
|
+
category: 'vue',
|
|
284
214
|
value: 'vue-basics',
|
|
285
|
-
name: 'vue-basics (
|
|
215
|
+
name: 'vue-basics (Template Syntax, v-model, CounterCard, UserForm & ItemList)',
|
|
216
|
+
desc: 'Core Vue 3 reactivity and directives divided into modular components'
|
|
286
217
|
},
|
|
287
218
|
{
|
|
288
|
-
category: 'vue
|
|
219
|
+
category: 'vue',
|
|
289
220
|
value: 'vue-props-emits',
|
|
290
|
-
name: 'vue-props-emits (
|
|
221
|
+
name: 'vue-props-emits (Pure Component Communication: ItemCard & ItemForm)',
|
|
222
|
+
desc: 'Parent-child communication without Axios or external libraries'
|
|
291
223
|
},
|
|
292
224
|
{
|
|
293
|
-
category: 'vue
|
|
225
|
+
category: 'vue',
|
|
294
226
|
value: 'vue-router',
|
|
295
|
-
name: 'vue-router (Vue Router
|
|
227
|
+
name: 'vue-router (Vue Router 4 Setup: NavBar, HomeHero & ItemsLayout)',
|
|
228
|
+
desc: 'Clean routing configuration with modular view components'
|
|
296
229
|
},
|
|
297
230
|
{
|
|
298
|
-
category: 'vue
|
|
231
|
+
category: 'vue',
|
|
299
232
|
value: 'vue-pinia',
|
|
300
|
-
name: 'vue-pinia (Pinia State
|
|
233
|
+
name: 'vue-pinia (Pinia State Management: ItemForm, ItemList & State Actions)',
|
|
234
|
+
desc: 'Reactive store getters, actions, and state modifications'
|
|
301
235
|
},
|
|
302
236
|
{
|
|
303
|
-
category: 'vue
|
|
237
|
+
category: 'vue',
|
|
304
238
|
value: 'vue-router-pinia',
|
|
305
|
-
name: 'vue-router-pinia (Router
|
|
239
|
+
name: 'vue-router-pinia (Router + Pinia: NavBar, Store Table & Modal Dialogs)',
|
|
240
|
+
desc: 'Integration of routing and global state without external HTTP'
|
|
306
241
|
},
|
|
307
242
|
{
|
|
308
|
-
category: 'vue
|
|
243
|
+
category: 'vue',
|
|
309
244
|
value: 'vue-router-pinia-props',
|
|
310
|
-
name: 'vue-router-pinia-props (Router + Pinia
|
|
245
|
+
name: 'vue-router-pinia-props (Router + Pinia + Child Props/Emits Architecture)',
|
|
246
|
+
desc: 'Hybrid architecture combining global store with prop-driven child components'
|
|
311
247
|
},
|
|
312
248
|
{
|
|
313
|
-
category: 'vue
|
|
249
|
+
category: 'vue',
|
|
314
250
|
value: 'vue-fetch',
|
|
315
|
-
name: 'vue-fetch (Native
|
|
251
|
+
name: 'vue-fetch (Native fetch() API with Modular Table, Form & Patch Modals)',
|
|
252
|
+
desc: 'Native browser fetch without Axios dependency, modular components included'
|
|
316
253
|
},
|
|
317
254
|
{
|
|
318
|
-
category: 'vue
|
|
319
|
-
value: 'vue-
|
|
320
|
-
name: 'vue-
|
|
255
|
+
category: 'vue',
|
|
256
|
+
value: 'vue-banking-app',
|
|
257
|
+
name: 'vue-banking-app (Account Overview, Transaction Form & History Table)',
|
|
258
|
+
desc: 'Banking UI with deposit, withdrawal, transaction ledger, and balance tracking'
|
|
321
259
|
},
|
|
322
260
|
{
|
|
323
|
-
category: 'vue
|
|
324
|
-
value: 'vue-
|
|
325
|
-
name: 'vue-
|
|
261
|
+
category: 'vue',
|
|
262
|
+
value: 'vue-cart-router-pinia',
|
|
263
|
+
name: 'vue-cart-router-pinia (E-Commerce Cart: Products, Cart Drawer & Badge)',
|
|
264
|
+
desc: 'Shopping cart with product listing, quantity updates, and cart calculations'
|
|
326
265
|
},
|
|
327
266
|
{
|
|
328
|
-
category: 'vue
|
|
329
|
-
value: 'vue-
|
|
330
|
-
name: 'vue-
|
|
267
|
+
category: 'vue',
|
|
268
|
+
value: 'vue-employee-system',
|
|
269
|
+
name: 'vue-employee-system (Employee Directory: Department Filter & Modal)',
|
|
270
|
+
desc: 'Staff management with department filtering, payroll calculator, and modals'
|
|
331
271
|
},
|
|
332
272
|
{
|
|
333
|
-
category: 'vue
|
|
273
|
+
category: 'vue',
|
|
334
274
|
value: 'vue-food-ordering',
|
|
335
|
-
name: 'vue-food-ordering (
|
|
275
|
+
name: 'vue-food-ordering (Restaurant App: Food Menu, Cart Summary & Order Modal)',
|
|
276
|
+
desc: 'Menu catalog with category filters, cart tally, tax computation, and checkout'
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
category: 'vue',
|
|
280
|
+
value: 'vue-hospital-portal',
|
|
281
|
+
name: 'vue-hospital-portal (Healthcare Portal: Doctor Cards, Admissions & Patients)',
|
|
282
|
+
desc: 'Patient admission portal with vitals scoring, doctor bookings, and discharge'
|
|
336
283
|
},
|
|
337
284
|
{
|
|
338
|
-
category: 'vue
|
|
285
|
+
category: 'vue',
|
|
286
|
+
value: 'vue-library-system',
|
|
287
|
+
name: 'vue-library-system (Library Catalog: Book Table, Inventory Form & Borrow Modal)',
|
|
288
|
+
desc: 'Book catalog with borrowing records, due date tracking, and fine calculation'
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
category: 'vue',
|
|
339
292
|
value: 'vue-symposium-system',
|
|
340
|
-
name: 'vue-symposium-system (
|
|
293
|
+
name: 'vue-symposium-system (Event Manager: Event Cards, Registration & Attendees)',
|
|
294
|
+
desc: 'College symposium event registration and attendee roster tracking'
|
|
341
295
|
},
|
|
342
296
|
{
|
|
343
|
-
category: 'vue
|
|
344
|
-
value: 'vue-
|
|
345
|
-
name: 'vue-
|
|
297
|
+
category: 'vue',
|
|
298
|
+
value: 'vue-todo-pinia',
|
|
299
|
+
name: 'vue-todo-pinia (Task Manager: Todo Input, Filter Controls & List)',
|
|
300
|
+
desc: 'Todo tracker with Pinia actions, completion toggles, and badge counters'
|
|
346
301
|
},
|
|
347
302
|
{
|
|
348
|
-
category: 'vue
|
|
349
|
-
value: 'vue-
|
|
350
|
-
name: 'vue-
|
|
303
|
+
category: 'vue',
|
|
304
|
+
value: 'vue-todo-router',
|
|
305
|
+
name: 'vue-todo-router (Multi-Page Todo: Todo NavBar, Task List & Form View)',
|
|
306
|
+
desc: 'Multi-route todo manager with parameter passing and task creation'
|
|
351
307
|
},
|
|
308
|
+
|
|
309
|
+
// 4. HTML5 & CSS3 Foundations (2)
|
|
352
310
|
{
|
|
353
|
-
category: '
|
|
354
|
-
value: '
|
|
355
|
-
name: '
|
|
311
|
+
category: 'html-css',
|
|
312
|
+
value: 'html-css-js',
|
|
313
|
+
name: 'html-css-js (HTML5 Semantic Layout, Form Validation & DOM Manipulation)',
|
|
314
|
+
desc: 'Pure vanilla web fundamentals with modern semantic HTML, CSS Flexbox/Grid, and JS validation'
|
|
356
315
|
},
|
|
357
316
|
{
|
|
358
|
-
category: '
|
|
359
|
-
value: '
|
|
360
|
-
name: '
|
|
317
|
+
category: 'html-css',
|
|
318
|
+
value: 'html-css-styles',
|
|
319
|
+
name: 'html-css-styles (50 Core CSS Properties Showcase & Playground)',
|
|
320
|
+
desc: '50 CSS properties reference: Flexbox, CSS Grid, Transitions, Keyframe Animations & Shadows'
|
|
361
321
|
},
|
|
362
322
|
|
|
363
|
-
// Games (9)
|
|
323
|
+
// 5. Interactive Browser Games (9)
|
|
364
324
|
{
|
|
365
325
|
category: 'games',
|
|
366
326
|
value: 'game-bingo',
|
|
367
|
-
name: 'game-bingo (
|
|
327
|
+
name: 'game-bingo (5x5 Randomized Bingo Grid with Win-Line Detection)',
|
|
328
|
+
desc: 'Classic 5x5 Bingo board with random number draw and winning line checks'
|
|
368
329
|
},
|
|
369
330
|
{
|
|
370
331
|
category: 'games',
|
|
371
|
-
value: 'game-
|
|
372
|
-
name: 'game-
|
|
332
|
+
value: 'game-memory-cards',
|
|
333
|
+
name: 'game-memory-cards (Card-Flip Pattern Matching Game in Vanilla JS)',
|
|
334
|
+
desc: 'Memory card matching with flip animations, move count, and match detection'
|
|
373
335
|
},
|
|
374
336
|
{
|
|
375
337
|
category: 'games',
|
|
376
|
-
value: 'game-
|
|
377
|
-
name: 'game-
|
|
338
|
+
value: 'game-rock-paper-scissors',
|
|
339
|
+
name: 'game-rock-paper-scissors (Classic Rock-Paper-Scissors with Score Tracker)',
|
|
340
|
+
desc: 'RPS game against computer AI with streak counting and score history'
|
|
378
341
|
},
|
|
379
342
|
{
|
|
380
343
|
category: 'games',
|
|
381
|
-
value: 'game-
|
|
382
|
-
name: 'game-
|
|
344
|
+
value: 'game-tic-tac-toe',
|
|
345
|
+
name: 'game-tic-tac-toe (2-Player Turn-Based Tic-Tac-Toe with Win Logic)',
|
|
346
|
+
desc: 'Classic 3x3 grid with turn toggling, win condition scanning, and reset'
|
|
383
347
|
},
|
|
384
348
|
{
|
|
385
349
|
category: 'games',
|
|
386
|
-
value: 'game-
|
|
387
|
-
name: 'game-
|
|
350
|
+
value: 'game-whack-a-mole',
|
|
351
|
+
name: 'game-whack-a-mole (Timed Click-Reaction Game with Score Counter)',
|
|
352
|
+
desc: 'Reaction game with randomized mole pops, timer countdown, and high scores'
|
|
388
353
|
},
|
|
389
354
|
{
|
|
390
355
|
category: 'games',
|
|
391
356
|
value: 'vue-game-rps',
|
|
392
|
-
name: 'vue-game-rps (Rock
|
|
357
|
+
name: 'vue-game-rps (Vue 3 Rock-Paper-Scissors with Reactive Scoreboard)',
|
|
358
|
+
desc: 'Vue 3 reactive game with computer AI choice and round history'
|
|
393
359
|
},
|
|
394
360
|
{
|
|
395
361
|
category: 'games',
|
|
396
362
|
value: 'vue-game-tictactoe',
|
|
397
|
-
name: 'vue-game-tictactoe (Tic
|
|
363
|
+
name: 'vue-game-tictactoe (Vue 3 Reactive Tic-Tac-Toe with Board State)',
|
|
364
|
+
desc: 'Vue 3 component game with computed winning lines and reset action'
|
|
398
365
|
},
|
|
399
366
|
{
|
|
400
367
|
category: 'games',
|
|
401
368
|
value: 'vue-game-whackamole',
|
|
402
|
-
name: 'vue-game-whackamole (Whack-
|
|
369
|
+
name: 'vue-game-whackamole (Vue 3 Whack-a-Mole with Reactive Timer & Score)',
|
|
370
|
+
desc: 'Vue 3 reactive game loop with timer interval and hit detection'
|
|
403
371
|
},
|
|
404
372
|
{
|
|
405
373
|
category: 'games',
|
|
406
374
|
value: 'vue-memory-game',
|
|
407
|
-
name: 'vue-memory-game (Card
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
// HTML / CSS (2)
|
|
411
|
-
{
|
|
412
|
-
category: 'html-css',
|
|
413
|
-
value: 'html-css-js',
|
|
414
|
-
name: 'html-css-js (Form Validation Regex, Required & Dynamic DOM)'
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
category: 'html-css',
|
|
418
|
-
value: 'html-css-styles',
|
|
419
|
-
name: 'html-css-styles (50 CSS Properties Showcase: Box Model, Flex, Grid, Animations)'
|
|
375
|
+
name: 'vue-memory-game (Vue 3 Card Match Game with Pinia State Store)',
|
|
376
|
+
desc: 'Vue 3 memory card flip game backed by Pinia store actions and timers'
|
|
420
377
|
}
|
|
421
378
|
];
|
|
422
379
|
|
|
423
|
-
const PAGE_SIZE = 12;
|
|
424
380
|
const templatesRoot = path.join(__dirname, '..', 'templates');
|
|
425
381
|
|
|
426
|
-
let currentView = 'MAIN'; // 'MAIN' | '
|
|
382
|
+
let currentView = 'MAIN'; // 'MAIN' | 'CATEGORY' | 'SEARCH' | 'ALL'
|
|
427
383
|
let activeCategory = null;
|
|
428
384
|
let searchQuery = '';
|
|
429
385
|
let selectedIndex = 0;
|
|
430
386
|
const selectedValues = new Set();
|
|
431
387
|
let isPrompting = false;
|
|
432
388
|
|
|
389
|
+
// ============================================================================
|
|
390
|
+
// MULTI-WORD SEARCH & ACTIVE ITEM RETRIEVAL
|
|
391
|
+
// ============================================================================
|
|
392
|
+
|
|
433
393
|
function getActiveItemList() {
|
|
434
394
|
if (currentView === 'MAIN') {
|
|
435
|
-
const list =
|
|
436
|
-
type: 'featured',
|
|
437
|
-
id: f.value,
|
|
438
|
-
name: f.name,
|
|
439
|
-
desc: f.desc
|
|
440
|
-
}));
|
|
441
|
-
list.push({ type: 'action', id: 'categories', name: '[C] Browse by Category (7 Groups, 56 Templates)', desc: 'Explore all 56 templates categorized into 7 groups' });
|
|
442
|
-
list.push({ type: 'action', id: 'search', name: '[S] Search by keyword across all 56 templates', desc: 'Type any keyword to find templates instantly' });
|
|
443
|
-
list.push({ type: 'action', id: 'all_view', name: '[L] View all 56 templates (grouped catalog)', desc: 'Browse the entire catalog with section dividers' });
|
|
444
|
-
list.push({ type: 'action', id: 'unpack_all', name: '[A] Unpack all 56 templates in 1-click', desc: 'Copy every single template into target folder' });
|
|
445
|
-
return list;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (currentView === 'CATEGORY_LIST') {
|
|
449
|
-
return categories.map((cat, idx) => {
|
|
395
|
+
const list = categories.map((cat, idx) => {
|
|
450
396
|
const count = templates.filter(t => t.category === cat.id).length;
|
|
451
397
|
return {
|
|
452
|
-
type: '
|
|
398
|
+
type: 'category',
|
|
453
399
|
id: cat.id,
|
|
400
|
+
key: String(idx + 1),
|
|
454
401
|
name: `[${idx + 1}] ${cat.title} (${count} templates)`,
|
|
455
402
|
desc: cat.desc
|
|
456
403
|
};
|
|
457
404
|
});
|
|
405
|
+
list.push({ type: 'action', id: 'search', key: 'S', name: '[S] Search Templates (Supports multi-word: e.g. "mongo join")', desc: 'Search across all templates with multiple keywords' });
|
|
406
|
+
list.push({ type: 'action', id: 'unpack_all', key: 'A', name: `[A] Unpack All ${templates.length} Templates in 1-Click`, desc: 'Extract the entire catalog into target directory' });
|
|
407
|
+
return list;
|
|
458
408
|
}
|
|
459
409
|
|
|
460
410
|
if (currentView === 'CATEGORY') {
|
|
@@ -464,11 +414,12 @@ function getActiveItemList() {
|
|
|
464
414
|
if (currentView === 'SEARCH') {
|
|
465
415
|
const q = searchQuery.toLowerCase().trim();
|
|
466
416
|
if (!q) return [];
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
t.
|
|
471
|
-
|
|
417
|
+
// Tokenized multi-word search
|
|
418
|
+
const tokens = q.split(/\s+/).filter(Boolean);
|
|
419
|
+
return templates.filter(t => {
|
|
420
|
+
const text = (t.value + ' ' + t.name + ' ' + (t.desc || '') + ' ' + t.category).toLowerCase();
|
|
421
|
+
return tokens.every(token => text.includes(token));
|
|
422
|
+
});
|
|
472
423
|
}
|
|
473
424
|
|
|
474
425
|
if (currentView === 'ALL') {
|
|
@@ -478,28 +429,27 @@ function getActiveItemList() {
|
|
|
478
429
|
return [];
|
|
479
430
|
}
|
|
480
431
|
|
|
432
|
+
// ============================================================================
|
|
433
|
+
// TERMINAL SCREEN RENDERING (ZERO EMOJIS)
|
|
434
|
+
// ============================================================================
|
|
435
|
+
|
|
481
436
|
function renderScreen() {
|
|
482
437
|
console.clear();
|
|
483
438
|
|
|
484
439
|
if (currentView === 'MAIN') {
|
|
485
440
|
console.log('========================================================');
|
|
486
|
-
console.log(' WAD LAB
|
|
487
|
-
console.log('
|
|
441
|
+
console.log(' WAD LAB TEMPLATES CLI ');
|
|
442
|
+
console.log(' 54 Clean Starter Templates + Tomcat Deploy ');
|
|
488
443
|
console.log('========================================================\n');
|
|
489
|
-
console.log('
|
|
490
|
-
console.log(' Press [1-
|
|
491
|
-
console.log('
|
|
444
|
+
console.log('MAIN MENU:');
|
|
445
|
+
console.log(' Press [1-5] to open category, [S] to Search, [A] for All');
|
|
446
|
+
console.log(' Navigate with [UP / DOWN], [ENTER] to confirm\n');
|
|
492
447
|
|
|
493
448
|
const items = getActiveItemList();
|
|
494
449
|
items.forEach((item, idx) => {
|
|
495
|
-
if (item.id === 'categories') {
|
|
496
|
-
console.log('\nFULL CATALOG & EXPLORATION:');
|
|
497
|
-
}
|
|
498
450
|
const isCursor = idx === selectedIndex;
|
|
499
|
-
const isChecked = selectedValues.has(item.id);
|
|
500
|
-
const box = item.type === 'featured' ? (isChecked ? '[x] ' : '[ ] ') : '';
|
|
501
451
|
const prefix = isCursor ? ' > ' : ' ';
|
|
502
|
-
console.log(prefix +
|
|
452
|
+
console.log(prefix + item.name);
|
|
503
453
|
});
|
|
504
454
|
|
|
505
455
|
const activeItem = items[selectedIndex];
|
|
@@ -507,302 +457,219 @@ function renderScreen() {
|
|
|
507
457
|
console.log('\n Info: ' + activeItem.desc);
|
|
508
458
|
}
|
|
509
459
|
if (selectedValues.size > 0) {
|
|
510
|
-
console.log(`\n Selected
|
|
511
|
-
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (currentView === 'CATEGORY_LIST') {
|
|
516
|
-
console.log('========================================================');
|
|
517
|
-
console.log(' Browse Templates by Category (56 Total)');
|
|
518
|
-
console.log('========================================================\n');
|
|
519
|
-
console.log(' Press [1-7] or [ENTER] to open category');
|
|
520
|
-
console.log(' [B / ESC] Back to Main Menu\n');
|
|
521
|
-
|
|
522
|
-
const items = getActiveItemList();
|
|
523
|
-
items.forEach((item, idx) => {
|
|
524
|
-
const isCursor = idx === selectedIndex;
|
|
525
|
-
const prefix = isCursor ? ' > ' : ' ';
|
|
526
|
-
console.log(prefix + item.name);
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
const activeItem = items[selectedIndex];
|
|
530
|
-
if (activeItem && activeItem.desc) {
|
|
531
|
-
console.log('\n Info: ' + activeItem.desc);
|
|
460
|
+
console.log(`\n Selected (${selectedValues.size}): [${Array.from(selectedValues).join(', ')}]`);
|
|
461
|
+
console.log(' Press [ENTER] to unpack selected, or [X] to clear selection.');
|
|
532
462
|
}
|
|
463
|
+
console.log('\n [1] Servlets | [2] Spring Boot | [3] Vue 3 | [4] HTML/CSS | [5] Games | [S] Search | [Q] Quit');
|
|
533
464
|
return;
|
|
534
465
|
}
|
|
535
466
|
|
|
536
467
|
if (currentView === 'CATEGORY') {
|
|
468
|
+
const items = getActiveItemList();
|
|
537
469
|
console.log('========================================================');
|
|
538
|
-
console.log(` ${activeCategory.title}`);
|
|
539
|
-
console.log(` ${activeCategory.desc}`);
|
|
470
|
+
console.log(` ${activeCategory.title} (${items.length} templates)`);
|
|
540
471
|
console.log('========================================================\n');
|
|
541
|
-
console.log(' [SPACE] Select /
|
|
542
|
-
console.log(' [ENTER] Confirm selection and proceed');
|
|
543
|
-
console.log(' [B / ESC] Back to Categories Menu\n');
|
|
472
|
+
console.log(' [SPACE] Select | [ENTER] Unpack | [B / ESC] Back to Main Menu\n');
|
|
544
473
|
|
|
545
|
-
const
|
|
474
|
+
const PAGE_SIZE = 12;
|
|
546
475
|
const total = items.length;
|
|
547
|
-
|
|
548
476
|
let start = selectedIndex - Math.floor(PAGE_SIZE / 2);
|
|
549
477
|
if (start < 0) start = 0;
|
|
550
|
-
if (start + PAGE_SIZE > total)
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
const end = Math.min(start + PAGE_SIZE, total);
|
|
478
|
+
if (start + PAGE_SIZE > total) start = Math.max(0, total - PAGE_SIZE);
|
|
479
|
+
const visibleItems = items.slice(start, start + PAGE_SIZE);
|
|
554
480
|
|
|
555
|
-
if (start > 0)
|
|
556
|
-
console.log(` ^ (${start} more above)`);
|
|
557
|
-
}
|
|
481
|
+
if (start > 0) console.log(' ... (scroll up)');
|
|
558
482
|
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
const isCursor =
|
|
483
|
+
visibleItems.forEach((t, i) => {
|
|
484
|
+
const realIndex = start + i;
|
|
485
|
+
const isCursor = realIndex === selectedIndex;
|
|
562
486
|
const isChecked = selectedValues.has(t.value);
|
|
563
|
-
const box = isChecked ? '[x]' : '[ ]';
|
|
487
|
+
const box = isChecked ? '[x] ' : '[ ] ';
|
|
564
488
|
const prefix = isCursor ? ' > ' : ' ';
|
|
565
|
-
console.log(prefix + box +
|
|
566
|
-
}
|
|
489
|
+
console.log(prefix + box + `[${realIndex + 1}] ` + t.name);
|
|
490
|
+
});
|
|
567
491
|
|
|
568
|
-
if (
|
|
569
|
-
console.log(` v (${total - end} more below)`);
|
|
570
|
-
}
|
|
492
|
+
if (start + PAGE_SIZE < total) console.log(' ... (scroll down)');
|
|
571
493
|
|
|
572
|
-
const
|
|
573
|
-
|
|
494
|
+
const activeItem = items[selectedIndex];
|
|
495
|
+
if (activeItem && activeItem.desc) {
|
|
496
|
+
console.log('\n Info: ' + activeItem.desc);
|
|
497
|
+
}
|
|
498
|
+
if (selectedValues.size > 0) {
|
|
499
|
+
console.log(`\n Selected: ${selectedValues.size} template(s) [Press ENTER to proceed]`);
|
|
500
|
+
}
|
|
574
501
|
return;
|
|
575
502
|
}
|
|
576
503
|
|
|
577
504
|
if (currentView === 'SEARCH') {
|
|
578
505
|
console.log('========================================================');
|
|
579
|
-
console.log('
|
|
580
|
-
console.log('
|
|
581
|
-
console.log(` Query: "${searchQuery}" (
|
|
582
|
-
console.log(' [SPACE] Select | [ENTER]
|
|
506
|
+
console.log(' Multi-Word Template Search');
|
|
507
|
+
console.log('========================================================\n');
|
|
508
|
+
console.log(` Query: "${searchQuery}" (Supports multi-word: e.g. "mongo join", "servlet jdbc")`);
|
|
509
|
+
console.log(' [SPACE] Select | [ENTER] Unpack | [ESC] Back to Main Menu\n');
|
|
583
510
|
|
|
584
511
|
const items = getActiveItemList();
|
|
585
512
|
if (!searchQuery.trim()) {
|
|
586
|
-
console.log(' Start typing to search across all
|
|
587
|
-
console.log(' Try
|
|
513
|
+
console.log(' Start typing keywords to search across all templates...');
|
|
514
|
+
console.log(' Try queries:');
|
|
515
|
+
console.log(' - "mongo join"');
|
|
516
|
+
console.log(' - "2model"');
|
|
517
|
+
console.log(' - "servlet jdbc"');
|
|
518
|
+
console.log(' - "vue pinia router"');
|
|
519
|
+
console.log(' - "patch"');
|
|
520
|
+
console.log(' - "aggregation"\n');
|
|
588
521
|
return;
|
|
589
522
|
}
|
|
590
523
|
|
|
591
524
|
if (items.length === 0) {
|
|
592
|
-
console.log(` No templates matched "${searchQuery}".`);
|
|
525
|
+
console.log(` No templates matched all tokens in "${searchQuery}".`);
|
|
593
526
|
return;
|
|
594
527
|
}
|
|
595
528
|
|
|
596
529
|
const total = items.length;
|
|
530
|
+
const PAGE_SIZE = 12;
|
|
597
531
|
let start = selectedIndex - Math.floor(PAGE_SIZE / 2);
|
|
598
532
|
if (start < 0) start = 0;
|
|
599
|
-
if (start + PAGE_SIZE > total)
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
const end = Math.min(start + PAGE_SIZE, total);
|
|
533
|
+
if (start + PAGE_SIZE > total) start = Math.max(0, total - PAGE_SIZE);
|
|
534
|
+
const visibleItems = items.slice(start, start + PAGE_SIZE);
|
|
603
535
|
|
|
604
|
-
if (start > 0)
|
|
605
|
-
console.log(` ^ (${start} more above)`);
|
|
606
|
-
}
|
|
536
|
+
if (start > 0) console.log(' ... (scroll up)');
|
|
607
537
|
|
|
608
|
-
|
|
609
|
-
const
|
|
610
|
-
const isCursor =
|
|
538
|
+
visibleItems.forEach((t, i) => {
|
|
539
|
+
const realIndex = start + i;
|
|
540
|
+
const isCursor = realIndex === selectedIndex;
|
|
611
541
|
const isChecked = selectedValues.has(t.value);
|
|
612
|
-
const box = isChecked ? '[x]' : '[ ]';
|
|
542
|
+
const box = isChecked ? '[x] ' : '[ ] ';
|
|
613
543
|
const prefix = isCursor ? ' > ' : ' ';
|
|
614
|
-
console.log(prefix + box +
|
|
615
|
-
}
|
|
544
|
+
console.log(prefix + box + t.name + ` [${t.category}]`);
|
|
545
|
+
});
|
|
616
546
|
|
|
617
|
-
if (
|
|
618
|
-
console.log(` v (${total - end} more below)`);
|
|
619
|
-
}
|
|
547
|
+
if (start + PAGE_SIZE < total) console.log(' ... (scroll down)');
|
|
620
548
|
|
|
621
|
-
|
|
549
|
+
const activeItem = items[selectedIndex];
|
|
550
|
+
if (activeItem && activeItem.desc) {
|
|
551
|
+
console.log('\n Info: ' + activeItem.desc);
|
|
552
|
+
}
|
|
553
|
+
if (selectedValues.size > 0) {
|
|
554
|
+
console.log(`\n Selected: ${selectedValues.size} template(s) [Press ENTER to proceed]`);
|
|
555
|
+
}
|
|
622
556
|
return;
|
|
623
557
|
}
|
|
558
|
+
}
|
|
624
559
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
console.log('========================================================\n');
|
|
629
|
-
console.log(' [SPACE] Select / Deselect | [ENTER] Confirm | [ESC] Back\n');
|
|
560
|
+
// ============================================================================
|
|
561
|
+
// COPY & EXTRACTION ENGINE
|
|
562
|
+
// ============================================================================
|
|
630
563
|
|
|
631
|
-
|
|
632
|
-
|
|
564
|
+
function copyDirectorySync(src, dest) {
|
|
565
|
+
if (!fs.existsSync(dest)) {
|
|
566
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
567
|
+
}
|
|
633
568
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
const end = Math.min(start + PAGE_SIZE, total);
|
|
569
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
570
|
+
for (const entry of entries) {
|
|
571
|
+
const srcPath = path.join(src, entry.name);
|
|
572
|
+
const destPath = path.join(dest, entry.name);
|
|
640
573
|
|
|
641
|
-
if (
|
|
642
|
-
|
|
574
|
+
if (entry.name === 'node_modules' || entry.name === 'target' || entry.name === '.git' || entry.name === 'dist') {
|
|
575
|
+
continue;
|
|
643
576
|
}
|
|
644
577
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
console.log(` --- ${catObj.title} ---`);
|
|
652
|
-
}
|
|
653
|
-
lastCat = t.category;
|
|
578
|
+
if (entry.isDirectory()) {
|
|
579
|
+
copyDirectorySync(srcPath, destPath);
|
|
580
|
+
} else {
|
|
581
|
+
fs.copyFileSync(srcPath, destPath);
|
|
582
|
+
if (entry.name === 'script.sh' || entry.name === 'mvnw') {
|
|
583
|
+
try { fs.chmodSync(destPath, 0o755); } catch (e) {}
|
|
654
584
|
}
|
|
655
|
-
const isCursor = i === selectedIndex;
|
|
656
|
-
const isChecked = selectedValues.has(t.value);
|
|
657
|
-
const box = isChecked ? '[x]' : '[ ]';
|
|
658
|
-
const prefix = isCursor ? ' > ' : ' ';
|
|
659
|
-
console.log(prefix + box + ' ' + t.name);
|
|
660
585
|
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function unpackTemplates(selected, folderName) {
|
|
590
|
+
const currentDir = process.cwd();
|
|
591
|
+
const targetDir = folderName && folderName !== '.'
|
|
592
|
+
? path.join(currentDir, folderName)
|
|
593
|
+
: currentDir;
|
|
594
|
+
|
|
595
|
+
console.log('\n--------------------------------------------------------');
|
|
596
|
+
console.log(`Extracting templates into: ${targetDir}`);
|
|
597
|
+
console.log('--------------------------------------------------------\n');
|
|
598
|
+
|
|
599
|
+
let listToUnpack = [];
|
|
600
|
+
|
|
601
|
+
if (selected.includes('all')) {
|
|
602
|
+
const allDirs = fs.readdirSync(templatesRoot).filter(f =>
|
|
603
|
+
fs.statSync(path.join(templatesRoot, f)).isDirectory()
|
|
604
|
+
);
|
|
605
|
+
listToUnpack = allDirs;
|
|
606
|
+
} else {
|
|
607
|
+
listToUnpack = selected;
|
|
608
|
+
}
|
|
661
609
|
|
|
662
|
-
|
|
663
|
-
|
|
610
|
+
let count = 0;
|
|
611
|
+
for (const tName of listToUnpack) {
|
|
612
|
+
const sourcePath = path.join(templatesRoot, tName);
|
|
613
|
+
if (!fs.existsSync(sourcePath)) {
|
|
614
|
+
console.log(`[SKIPPED] Template not found: ${tName}`);
|
|
615
|
+
continue;
|
|
664
616
|
}
|
|
665
617
|
|
|
666
|
-
|
|
618
|
+
let destPath = targetDir;
|
|
619
|
+
if (listToUnpack.length > 1) {
|
|
620
|
+
destPath = path.join(targetDir, tName);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
try {
|
|
624
|
+
copyDirectorySync(sourcePath, destPath);
|
|
625
|
+
console.log(`[COPIED] ${tName} -> ${destPath}`);
|
|
626
|
+
count++;
|
|
627
|
+
} catch (err) {
|
|
628
|
+
console.error(`[ERROR] Failed to copy ${tName}: ${err.message}`);
|
|
629
|
+
}
|
|
667
630
|
}
|
|
631
|
+
|
|
632
|
+
console.log('\n========================================================');
|
|
633
|
+
console.log(`SUCCESS: ${count} template(s) unpacked cleanly.`);
|
|
634
|
+
console.log('========================================================');
|
|
635
|
+
console.log('Ready for development.\n');
|
|
636
|
+
process.exit(0);
|
|
668
637
|
}
|
|
669
638
|
|
|
670
|
-
function promptFolderName(
|
|
639
|
+
function promptFolderName(selected) {
|
|
671
640
|
isPrompting = true;
|
|
672
|
-
process.stdin.removeAllListeners('keypress');
|
|
673
641
|
if (process.stdin.isTTY) {
|
|
674
642
|
process.stdin.setRawMode(false);
|
|
675
643
|
}
|
|
676
644
|
|
|
645
|
+
const defaultFolder = selected.length === 1 && selected[0] !== 'all' ? selected[0] : '.';
|
|
677
646
|
const rl = readline.createInterface({
|
|
678
647
|
input: process.stdin,
|
|
679
648
|
output: process.stdout
|
|
680
649
|
});
|
|
681
650
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
});
|
|
688
|
-
rl.question('\nChoose frontend (1-11, default: 1 [all]): ', (ans) => {
|
|
689
|
-
const val = ans.trim();
|
|
690
|
-
let selectedFrontend = 'all';
|
|
691
|
-
const num = parseInt(val, 10);
|
|
692
|
-
if (!isNaN(num) && num >= 1 && num <= frontendOptions.length) {
|
|
693
|
-
selectedFrontend = frontendOptions[num - 1].value;
|
|
694
|
-
} else {
|
|
695
|
-
const directMatch = frontendOptions.find(f => f.value === val);
|
|
696
|
-
if (directMatch) {
|
|
697
|
-
selectedFrontend = directMatch.value;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
done(selectedFrontend);
|
|
701
|
-
});
|
|
702
|
-
} else {
|
|
703
|
-
done(null);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
askFrontendIfRequired((selectedFrontend) => {
|
|
708
|
-
const isAll = choices.includes('all');
|
|
709
|
-
const defaultFolder = isAll || choices.length > 1 ? 'wad-templates' : choices[0];
|
|
710
|
-
|
|
711
|
-
console.log('\nSelected to unpack: ' + choices.join(', '));
|
|
712
|
-
if (selectedFrontend) {
|
|
713
|
-
console.log('Selected frontend variant: ' + selectedFrontend);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
rl.question(`Enter destination folder name (default: "${defaultFolder}"): `, (answer) => {
|
|
717
|
-
rl.close();
|
|
718
|
-
const folder = answer.trim() || defaultFolder;
|
|
719
|
-
const destDir = path.resolve(process.cwd(), folder);
|
|
720
|
-
|
|
721
|
-
if (!fs.existsSync(destDir)) {
|
|
722
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
if (isAll) {
|
|
726
|
-
copyDirRecursive(templatesRoot, destDir);
|
|
727
|
-
console.log('\nSuccessfully unpacked ALL 56 templates into: ' + destDir);
|
|
728
|
-
} else {
|
|
729
|
-
choices.forEach(c => {
|
|
730
|
-
if (c === 'fullstack-springboot-vue') {
|
|
731
|
-
const targetFullstack = choices.length === 1 ? destDir : path.join(destDir, c);
|
|
732
|
-
if (!fs.existsSync(targetFullstack)) {
|
|
733
|
-
fs.mkdirSync(targetFullstack, { recursive: true });
|
|
734
|
-
}
|
|
735
|
-
const backendSrc = path.join(templatesRoot, 'springboot-2model-1controller-api');
|
|
736
|
-
const backendDest = path.join(targetFullstack, 'backend');
|
|
737
|
-
copyDirRecursive(backendSrc, backendDest);
|
|
738
|
-
console.log('Unpacked [backend] (Spring Boot 2-Model API) into: ' + backendDest);
|
|
739
|
-
|
|
740
|
-
const frontendDest = path.join(targetFullstack, 'frontend');
|
|
741
|
-
if (selectedFrontend === 'all') {
|
|
742
|
-
const fullstackTemplateFrontendSrc = path.join(templatesRoot, 'fullstack-springboot-vue', 'frontend');
|
|
743
|
-
copyDirRecursive(fullstackTemplateFrontendSrc, frontendDest);
|
|
744
|
-
console.log('Unpacked [frontend] (All 10 Vue variants) into: ' + frontendDest);
|
|
745
|
-
} else {
|
|
746
|
-
const singleFrontendSrc = path.join(templatesRoot, selectedFrontend);
|
|
747
|
-
copyDirRecursive(singleFrontendSrc, frontendDest);
|
|
748
|
-
console.log('Unpacked [frontend] (' + selectedFrontend + ') into: ' + frontendDest);
|
|
749
|
-
}
|
|
750
|
-
} else {
|
|
751
|
-
const src = path.join(templatesRoot, c);
|
|
752
|
-
const dest = path.join(destDir, c);
|
|
753
|
-
copyDirRecursive(src, dest);
|
|
754
|
-
console.log('Unpacked [' + c + '] into: ' + dest);
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
console.log('\nSuccessfully unpacked all chosen templates into: ' + destDir);
|
|
758
|
-
}
|
|
759
|
-
process.exit(0);
|
|
760
|
-
});
|
|
651
|
+
console.log('\n');
|
|
652
|
+
rl.question(`Enter destination directory (default: "${defaultFolder}"): `, (answer) => {
|
|
653
|
+
rl.close();
|
|
654
|
+
const folder = answer.trim() || defaultFolder;
|
|
655
|
+
unpackTemplates(selected, folder);
|
|
761
656
|
});
|
|
762
657
|
}
|
|
763
658
|
|
|
764
|
-
//
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
if (arg === 'all' || arg === '--all' || arg === '-a') {
|
|
768
|
-
promptFolderName(['all']);
|
|
769
|
-
} else if (arg === '--cat' && process.argv[3]) {
|
|
770
|
-
const catId = process.argv[3].trim().toLowerCase();
|
|
771
|
-
const foundCat = categories.find(c => c.id === catId);
|
|
772
|
-
if (foundCat) {
|
|
773
|
-
activeCategory = foundCat;
|
|
774
|
-
currentView = 'CATEGORY';
|
|
775
|
-
selectedIndex = 0;
|
|
776
|
-
startInteractive();
|
|
777
|
-
} else {
|
|
778
|
-
console.error(`Unknown category: "${catId}". Available: ${categories.map(c => c.id).join(', ')}`);
|
|
779
|
-
process.exit(1);
|
|
780
|
-
}
|
|
781
|
-
} else if (arg === '--search' && process.argv[3]) {
|
|
782
|
-
searchQuery = process.argv.slice(3).join(' ');
|
|
783
|
-
currentView = 'SEARCH';
|
|
784
|
-
selectedIndex = 0;
|
|
785
|
-
startInteractive();
|
|
786
|
-
} else {
|
|
787
|
-
const found = templates.find(t => t.value === arg);
|
|
788
|
-
if (found) {
|
|
789
|
-
promptFolderName([found.value]);
|
|
790
|
-
} else {
|
|
791
|
-
console.error('Unknown template: ' + arg);
|
|
792
|
-
console.error(`Available templates (56 total) or run without arguments for the interactive menu.`);
|
|
793
|
-
process.exit(1);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
} else {
|
|
797
|
-
startInteractive();
|
|
798
|
-
}
|
|
659
|
+
// ============================================================================
|
|
660
|
+
// INTERACTIVE INPUT HANDLER
|
|
661
|
+
// ============================================================================
|
|
799
662
|
|
|
800
663
|
function startInteractive() {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
process.
|
|
664
|
+
if (!process.stdin.isTTY) {
|
|
665
|
+
console.log('Interactive mode requires a TTY terminal. Usage: npx wad-lab [template-name]');
|
|
666
|
+
process.exit(1);
|
|
804
667
|
}
|
|
805
668
|
|
|
669
|
+
readline.emitKeypressEvents(process.stdin);
|
|
670
|
+
process.stdin.setRawMode(true);
|
|
671
|
+
process.stdin.resume();
|
|
672
|
+
|
|
806
673
|
renderScreen();
|
|
807
674
|
|
|
808
675
|
process.stdin.on('keypress', (str, key) => {
|
|
@@ -812,20 +679,18 @@ function startInteractive() {
|
|
|
812
679
|
process.exit(0);
|
|
813
680
|
}
|
|
814
681
|
|
|
815
|
-
const items = getActiveItemList();
|
|
816
|
-
|
|
817
682
|
// MAIN VIEW INPUT
|
|
818
683
|
if (currentView === 'MAIN') {
|
|
684
|
+
const items = getActiveItemList();
|
|
685
|
+
|
|
819
686
|
if (key.name === 'up') {
|
|
820
687
|
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
821
688
|
renderScreen();
|
|
822
689
|
} else if (key.name === 'down') {
|
|
823
690
|
selectedIndex = (selectedIndex + 1) % items.length;
|
|
824
691
|
renderScreen();
|
|
825
|
-
} else if (str === '
|
|
826
|
-
|
|
827
|
-
selectedIndex = 0;
|
|
828
|
-
renderScreen();
|
|
692
|
+
} else if (str === 'q' || str === 'Q') {
|
|
693
|
+
process.exit(0);
|
|
829
694
|
} else if (str === 's' || str === 'S') {
|
|
830
695
|
currentView = 'SEARCH';
|
|
831
696
|
searchQuery = '';
|
|
@@ -833,36 +698,23 @@ function startInteractive() {
|
|
|
833
698
|
renderScreen();
|
|
834
699
|
} else if (str === 'a' || str === 'A') {
|
|
835
700
|
promptFolderName(['all']);
|
|
836
|
-
} else if (str
|
|
837
|
-
currentView = 'ALL';
|
|
838
|
-
selectedIndex = 0;
|
|
839
|
-
renderScreen();
|
|
840
|
-
} else if (str >= '1' && str <= '7') {
|
|
701
|
+
} else if (str >= '1' && str <= '5') {
|
|
841
702
|
const num = parseInt(str, 10);
|
|
842
|
-
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
} else if (key.name === 'space') {
|
|
847
|
-
const item = items[selectedIndex];
|
|
848
|
-
if (item.type === 'featured') {
|
|
849
|
-
if (selectedValues.has(item.id)) {
|
|
850
|
-
selectedValues.delete(item.id);
|
|
851
|
-
} else {
|
|
852
|
-
selectedValues.add(item.id);
|
|
853
|
-
}
|
|
703
|
+
activeCategory = categories[num - 1];
|
|
704
|
+
if (activeCategory) {
|
|
705
|
+
currentView = 'CATEGORY';
|
|
706
|
+
selectedIndex = 0;
|
|
854
707
|
renderScreen();
|
|
855
708
|
}
|
|
856
709
|
} else if (key.name === 'return' || key.name === 'enter') {
|
|
857
|
-
const item = items[selectedIndex];
|
|
858
710
|
if (selectedValues.size > 0) {
|
|
859
711
|
promptFolderName(Array.from(selectedValues));
|
|
860
712
|
return;
|
|
861
713
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
currentView = '
|
|
714
|
+
const item = items[selectedIndex];
|
|
715
|
+
if (item.type === 'category') {
|
|
716
|
+
activeCategory = categories.find(c => c.id === item.id);
|
|
717
|
+
currentView = 'CATEGORY';
|
|
866
718
|
selectedIndex = 0;
|
|
867
719
|
renderScreen();
|
|
868
720
|
} else if (item.id === 'search') {
|
|
@@ -870,19 +722,20 @@ function startInteractive() {
|
|
|
870
722
|
searchQuery = '';
|
|
871
723
|
selectedIndex = 0;
|
|
872
724
|
renderScreen();
|
|
873
|
-
} else if (item.id === 'all_view') {
|
|
874
|
-
currentView = 'ALL';
|
|
875
|
-
selectedIndex = 0;
|
|
876
|
-
renderScreen();
|
|
877
725
|
} else if (item.id === 'unpack_all') {
|
|
878
726
|
promptFolderName(['all']);
|
|
879
727
|
}
|
|
728
|
+
} else if (str === 'x' || str === 'X') {
|
|
729
|
+
selectedValues.clear();
|
|
730
|
+
renderScreen();
|
|
880
731
|
}
|
|
881
732
|
return;
|
|
882
733
|
}
|
|
883
734
|
|
|
884
|
-
//
|
|
885
|
-
if (currentView === '
|
|
735
|
+
// CATEGORY VIEW INPUT
|
|
736
|
+
if (currentView === 'CATEGORY') {
|
|
737
|
+
const items = getActiveItemList();
|
|
738
|
+
|
|
886
739
|
if (key.name === 'escape' || key.name === 'left' || str === 'b' || str === 'B') {
|
|
887
740
|
currentView = 'MAIN';
|
|
888
741
|
selectedIndex = 0;
|
|
@@ -895,23 +748,28 @@ function startInteractive() {
|
|
|
895
748
|
} else if (key.name === 'down') {
|
|
896
749
|
selectedIndex = (selectedIndex + 1) % items.length;
|
|
897
750
|
renderScreen();
|
|
898
|
-
} else if (
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
751
|
+
} else if (key.name === 'space') {
|
|
752
|
+
const t = items[selectedIndex];
|
|
753
|
+
if (t) {
|
|
754
|
+
if (selectedValues.has(t.value)) {
|
|
755
|
+
selectedValues.delete(t.value);
|
|
756
|
+
} else {
|
|
757
|
+
selectedValues.add(t.value);
|
|
758
|
+
}
|
|
759
|
+
renderScreen();
|
|
760
|
+
}
|
|
904
761
|
} else if (key.name === 'return' || key.name === 'enter') {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
762
|
+
if (selectedValues.size > 0) {
|
|
763
|
+
promptFolderName(Array.from(selectedValues));
|
|
764
|
+
} else {
|
|
765
|
+
const t = items[selectedIndex];
|
|
766
|
+
if (t) promptFolderName([t.value]);
|
|
767
|
+
}
|
|
910
768
|
}
|
|
911
769
|
return;
|
|
912
770
|
}
|
|
913
771
|
|
|
914
|
-
// SEARCH VIEW INPUT
|
|
772
|
+
// SEARCH VIEW INPUT (MULTI-WORD)
|
|
915
773
|
if (currentView === 'SEARCH') {
|
|
916
774
|
if (key.name === 'escape') {
|
|
917
775
|
currentView = 'MAIN';
|
|
@@ -920,43 +778,31 @@ function startInteractive() {
|
|
|
920
778
|
return;
|
|
921
779
|
}
|
|
922
780
|
|
|
923
|
-
|
|
924
|
-
if (items.length > 0) {
|
|
925
|
-
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
926
|
-
renderScreen();
|
|
927
|
-
}
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
781
|
+
const items = getActiveItemList();
|
|
930
782
|
|
|
931
|
-
if (key.name === '
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
renderScreen();
|
|
935
|
-
}
|
|
783
|
+
if (key.name === 'up' && items.length > 0) {
|
|
784
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
785
|
+
renderScreen();
|
|
936
786
|
return;
|
|
937
787
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
const currentVal = items[selectedIndex].value;
|
|
942
|
-
if (selectedValues.has(currentVal)) {
|
|
943
|
-
selectedValues.delete(currentVal);
|
|
944
|
-
} else {
|
|
945
|
-
selectedValues.add(currentVal);
|
|
946
|
-
}
|
|
947
|
-
renderScreen();
|
|
948
|
-
}
|
|
788
|
+
if (key.name === 'down' && items.length > 0) {
|
|
789
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
790
|
+
renderScreen();
|
|
949
791
|
return;
|
|
950
792
|
}
|
|
951
793
|
|
|
952
794
|
if (key.name === 'return' || key.name === 'enter') {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
795
|
+
if (selectedValues.size > 0) {
|
|
796
|
+
promptFolderName(Array.from(selectedValues));
|
|
797
|
+
return;
|
|
956
798
|
}
|
|
957
|
-
if (
|
|
958
|
-
promptFolderName(
|
|
799
|
+
if (items.length > 0) {
|
|
800
|
+
promptFolderName([items[selectedIndex].value]);
|
|
801
|
+
return;
|
|
959
802
|
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (key.name === 'space' && key.ctrl) {
|
|
960
806
|
return;
|
|
961
807
|
}
|
|
962
808
|
|
|
@@ -973,63 +819,67 @@ function startInteractive() {
|
|
|
973
819
|
return;
|
|
974
820
|
}
|
|
975
821
|
|
|
976
|
-
// Typing search
|
|
977
|
-
if (str &&
|
|
822
|
+
// Typing characters into search (allows spaces for multi-word search!)
|
|
823
|
+
if (str && !key.ctrl && !key.meta) {
|
|
978
824
|
searchQuery += str;
|
|
979
825
|
selectedIndex = 0;
|
|
980
826
|
renderScreen();
|
|
981
827
|
return;
|
|
982
828
|
}
|
|
983
|
-
return;
|
|
984
829
|
}
|
|
830
|
+
});
|
|
831
|
+
}
|
|
985
832
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
currentView = currentView === 'CATEGORY' ? 'CATEGORY_LIST' : 'MAIN';
|
|
990
|
-
selectedIndex = 0;
|
|
991
|
-
renderScreen();
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
833
|
+
// ============================================================================
|
|
834
|
+
// CLI ARGUMENT DISPATCHER
|
|
835
|
+
// ============================================================================
|
|
994
836
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
998
|
-
renderScreen();
|
|
999
|
-
}
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
837
|
+
function runCli() {
|
|
838
|
+
const arg = process.argv[2];
|
|
1002
839
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
840
|
+
if (!arg) {
|
|
841
|
+
startInteractive();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
1010
844
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
}
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
845
|
+
if (arg === '--help' || arg === '-h') {
|
|
846
|
+
console.log('WAD Lab Templates CLI (Zero Emojis)');
|
|
847
|
+
console.log('Usage:');
|
|
848
|
+
console.log(' npx wad-lab Launch interactive UI');
|
|
849
|
+
console.log(' npx wad-lab [template-name] Directly unpack a template');
|
|
850
|
+
console.log(' npx wad-lab --list List all available templates');
|
|
851
|
+
console.log(' npx wad-lab --search [words...] Search templates with multi-word filter');
|
|
852
|
+
process.exit(0);
|
|
853
|
+
}
|
|
1023
854
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
855
|
+
if (arg === '--list' || arg === '-l') {
|
|
856
|
+
console.log('Available WAD Lab Templates (54 Standalone):');
|
|
857
|
+
categories.forEach(cat => {
|
|
858
|
+
console.log(`\n=== ${cat.title} ===`);
|
|
859
|
+
templates.filter(t => t.category === cat.id).forEach(t => {
|
|
860
|
+
console.log(` - ${t.value.padEnd(38)} : ${t.desc}`);
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
process.exit(0);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (arg === '--search' && process.argv[3]) {
|
|
867
|
+
searchQuery = process.argv.slice(3).join(' ');
|
|
868
|
+
currentView = 'SEARCH';
|
|
869
|
+
selectedIndex = 0;
|
|
870
|
+
startInteractive();
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const found = templates.find(t => t.value === arg);
|
|
875
|
+
if (found) {
|
|
876
|
+
const dest = process.argv[3] || found.value;
|
|
877
|
+
unpackTemplates([found.value], dest);
|
|
878
|
+
} else {
|
|
879
|
+
console.log(`Unknown template or command: "${arg}".`);
|
|
880
|
+
console.log('Run "npx wad-lab --list" to view all available templates.');
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
1035
883
|
}
|
|
884
|
+
|
|
885
|
+
runCli();
|