@abuhannaa/create-apptemplate 1.0.12 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,679 +4,13 @@
4
4
  import { intro, outro, isCancel as isCancel2 } from "@clack/prompts";
5
5
  import pc3 from "picocolors";
6
6
 
7
- // src/types.ts
8
- var ALL_FEATURES = [
9
- "auth",
10
- "userManagement",
11
- "departments",
12
- "fileUpload",
13
- "auditLogs",
14
- "notifications",
15
- "dataExport",
16
- "dashboard"
17
- ];
18
-
19
- // src/features.ts
20
- var FEATURE_OPTIONS = [
21
- {
22
- value: "auth",
23
- label: "Authentication (Login, JWT, Guards)",
24
- hint: "Login/logout pages, JWT middleware, auth guards, profile"
25
- },
26
- {
27
- value: "userManagement",
28
- label: "User Management",
29
- hint: "User CRUD, roles, permissions"
30
- },
31
- {
32
- value: "departments",
33
- label: "Departments",
34
- hint: "Department CRUD module"
35
- },
36
- {
37
- value: "fileUpload",
38
- label: "File Management",
39
- hint: "File upload, download, preview"
40
- },
41
- {
42
- value: "auditLogs",
43
- label: "Audit Logging",
44
- hint: "Track all data changes with audit trail"
45
- },
46
- {
47
- value: "notifications",
48
- label: "Notifications",
49
- hint: "Real-time notification system (WebSocket)"
50
- },
51
- {
52
- value: "dataExport",
53
- label: "Data Export",
54
- hint: "CSV, Excel, PDF export"
55
- },
56
- {
57
- value: "dashboard",
58
- label: "Dashboard",
59
- hint: "Dashboard page with statistics (Requires Users, Depts, Notifications)"
60
- }
61
- ];
62
- function applyFeatureDependencies(selected) {
63
- let result = [...selected];
64
- if (!result.includes("auth")) {
65
- result = result.filter((f) => f !== "userManagement" && f !== "departments");
66
- }
67
- if (!result.includes("userManagement")) {
68
- result = result.filter((f) => f !== "departments");
69
- }
70
- if (!result.includes("userManagement") || !result.includes("departments") || !result.includes("notifications")) {
71
- result = result.filter((f) => f !== "dashboard");
72
- }
73
- if (!result.includes("userManagement") || !result.includes("departments")) {
74
- result = result.filter((f) => f !== "dataExport");
75
- }
76
- return result;
77
- }
78
- var DOTNET_CLEAN_MAP = {
79
- auth: {
80
- backend: [
81
- // Application layer
82
- "src/Core/App.Template.Application/Features/Authentication",
83
- "src/Core/App.Template.Application/DTOs/Auth",
84
- "src/Core/App.Template.Application/Interfaces/IJwtTokenService.cs",
85
- "src/Core/App.Template.Application/Interfaces/IPasswordHashService.cs",
86
- "src/Core/App.Template.Application/Interfaces/ISsoAuthService.cs",
87
- "src/Core/App.Template.Application/Interfaces/IEmailService.cs",
88
- // Infrastructure layer
89
- "src/Infrastructure/App.Template.Infrastructure/Services/JwtTokenService.cs",
90
- "src/Infrastructure/App.Template.Infrastructure/Services/PasswordHashService.cs",
91
- "src/Infrastructure/App.Template.Infrastructure/Services/SsoAuthService.cs",
92
- "src/Infrastructure/App.Template.Infrastructure/Services/SsoApiModels.cs",
93
- "src/Infrastructure/App.Template.Infrastructure/Services/EmailService.cs",
94
- // Presentation layer
95
- "src/Presentation/App.Template.WebAPI/Controllers/AuthController.cs",
96
- "src/Core/App.Template.Application/DTOs/UserDto.cs"
97
- // Required by Auth
98
- ],
99
- frontend: []
100
- // Frontend auth files handled by pattern below
101
- },
102
- userManagement: {
103
- backend: [
104
- "src/Core/App.Template.Application/Features/UserManagement",
105
- // 'src/Core/App.Template.Application/DTOs/UserDto.cs', // Moved to auth
106
- "src/Presentation/App.Template.WebAPI/Controllers/UsersController.cs"
107
- ],
108
- frontend: []
109
- },
110
- departments: {
111
- backend: [
112
- "src/Core/App.Template.Application/Features/DepartmentManagement",
113
- "src/Core/App.Template.Application/DTOs/DepartmentDto.cs",
114
- "src/Core/App.Template.Application/Interfaces/IOrganizationService.cs",
115
- "src/Infrastructure/App.Template.Infrastructure/Services/OrganizationService.cs",
116
- "src/Presentation/App.Template.WebAPI/Controllers/DepartmentsController.cs"
117
- ],
118
- frontend: []
119
- },
120
- fileUpload: {
121
- backend: [
122
- "src/Core/App.Template.Application/Features/FileManagement",
123
- "src/Core/App.Template.Application/DTOs/UploadedFileDto.cs",
124
- "src/Core/App.Template.Application/Interfaces/IFileStorageService.cs",
125
- "src/Infrastructure/App.Template.Infrastructure/Services/FileStorageService.cs",
126
- "src/Presentation/App.Template.WebAPI/Controllers/FilesController.cs"
127
- ],
128
- frontend: []
129
- },
130
- auditLogs: {
131
- backend: [
132
- "src/Core/App.Template.Application/Features/AuditLogManagement",
133
- "src/Core/App.Template.Application/DTOs/AuditLogDto.cs",
134
- "src/Infrastructure/App.Template.Infrastructure/Persistence/AuditEntry.cs",
135
- "src/Presentation/App.Template.WebAPI/Controllers/AuditLogsController.cs"
136
- ],
137
- frontend: []
138
- },
139
- notifications: {
140
- backend: [
141
- "src/Core/App.Template.Application/Features/NotificationManagement",
142
- "src/Core/App.Template.Application/DTOs/NotificationDto.cs",
143
- "src/Core/App.Template.Application/Interfaces/INotificationService.cs",
144
- "src/Infrastructure/App.Template.Infrastructure/Services/NotificationService.cs",
145
- "src/Infrastructure/App.Template.Infrastructure/Hubs",
146
- "src/Infrastructure/App.Template.Infrastructure/SignalR",
147
- "src/Presentation/App.Template.WebAPI/Controllers/NotificationsController.cs"
148
- ],
149
- frontend: []
150
- },
151
- dataExport: {
152
- backend: [
153
- "src/Core/App.Template.Application/Interfaces/IExportService.cs",
154
- "src/Infrastructure/App.Template.Infrastructure/Services/ExportService.cs",
155
- "src/Presentation/App.Template.WebAPI/Controllers/ExportController.cs"
156
- ],
157
- frontend: []
158
- },
159
- dashboard: {
160
- backend: [],
161
- frontend: []
162
- }
163
- };
164
- var DOTNET_NLAYER_MAP = {
165
- auth: {
166
- backend: [
167
- "src/App.Template.Api/Controllers/AuthController.cs",
168
- "src/App.Template.Api/Services/AuthService.cs",
169
- "src/App.Template.Api/Services/JwtTokenGenerator.cs",
170
- "src/App.Template.Api/Models/Dtos/AuthDtos.cs",
171
- "src/App.Template.Api/Models/Dtos/UserDtos.cs"
172
- // Required by Auth
173
- ],
174
- frontend: []
175
- },
176
- userManagement: {
177
- backend: [
178
- "src/App.Template.Api/Controllers/UsersController.cs",
179
- "src/App.Template.Api/Services/UserService.cs",
180
- "src/App.Template.Api/Services/IUserService.cs",
181
- "src/App.Template.Api/Repositories/UserRepository.cs",
182
- "src/App.Template.Api/Repositories/IUserRepository.cs"
183
- // 'src/App.Template.Api/Models/Dtos/UserDtos.cs', // Moved to auth
184
- ],
185
- frontend: []
186
- },
187
- departments: {
188
- backend: [
189
- "src/App.Template.Api/Controllers/DepartmentsController.cs",
190
- "src/App.Template.Api/Models/Dtos/DepartmentDtos.cs"
191
- ],
192
- frontend: []
193
- },
194
- fileUpload: {
195
- backend: [
196
- "src/App.Template.Api/Controllers/FilesController.cs"
197
- ],
198
- frontend: []
199
- },
200
- auditLogs: {
201
- backend: [],
202
- frontend: []
203
- },
204
- notifications: {
205
- backend: [
206
- "src/App.Template.Api/Controllers/NotificationsController.cs",
207
- "src/App.Template.Api/Infrastructure/Hubs"
208
- ],
209
- frontend: []
210
- },
211
- dataExport: {
212
- backend: [
213
- "src/App.Template.Api/Controllers/ExportController.cs"
214
- ],
215
- frontend: []
216
- },
217
- dashboard: {
218
- backend: [],
219
- frontend: []
220
- }
221
- };
222
- var DOTNET_FEATURE_MAP = {
223
- auth: {
224
- backend: [
225
- "src/App.Template.Api/Features/Auth"
226
- ],
227
- frontend: []
228
- },
229
- userManagement: {
230
- backend: [
231
- // 'src/App.Template.Api/Features/Users', // Don't remove whole folder, Auth needs Entity/Repo/DTOs
232
- "src/App.Template.Api/Features/Users/UsersController.cs",
233
- "src/App.Template.Api/Features/Users/UserService.cs",
234
- "src/App.Template.Api/Features/Users/IUserService.cs"
235
- ],
236
- frontend: []
237
- },
238
- departments: {
239
- backend: [
240
- "src/App.Template.Api/Features/Departments"
241
- ],
242
- frontend: []
243
- },
244
- fileUpload: {
245
- backend: [
246
- "src/App.Template.Api/Features/Files"
247
- ],
248
- frontend: []
249
- },
250
- auditLogs: {
251
- backend: [],
252
- frontend: []
253
- },
254
- notifications: {
255
- backend: [
256
- "src/App.Template.Api/Features/Notifications"
257
- ],
258
- frontend: []
259
- },
260
- dataExport: {
261
- backend: [
262
- "src/App.Template.Api/Features/Export"
263
- ],
264
- frontend: []
265
- },
266
- dashboard: {
267
- backend: [],
268
- frontend: []
269
- }
270
- };
271
- var SPRING_CLEAN_MAP = {
272
- auth: {
273
- backend: [
274
- "api/src/main/java/apptemplate/api/controllers/AuthController.java"
275
- ],
276
- frontend: []
277
- },
278
- userManagement: {
279
- backend: [
280
- "api/src/main/java/apptemplate/api/controllers/UsersController.java"
281
- ],
282
- frontend: []
283
- },
284
- departments: {
285
- backend: [
286
- "api/src/main/java/apptemplate/api/controllers/DepartmentsController.java"
287
- ],
288
- frontend: []
289
- },
290
- fileUpload: {
291
- backend: [
292
- "api/src/main/java/apptemplate/api/controllers/FilesController.java"
293
- ],
294
- frontend: []
295
- },
296
- auditLogs: {
297
- backend: [
298
- "api/src/main/java/apptemplate/api/controllers/AuditLogsController.java"
299
- ],
300
- frontend: []
301
- },
302
- notifications: {
303
- backend: [
304
- "api/src/main/java/apptemplate/api/controllers/NotificationsController.java"
305
- ],
306
- frontend: []
307
- },
308
- dataExport: {
309
- backend: [
310
- "api/src/main/java/apptemplate/api/controllers/ExportController.java"
311
- ],
312
- frontend: []
313
- },
314
- dashboard: {
315
- backend: [],
316
- frontend: []
317
- }
318
- };
319
- var SPRING_NLAYER_MAP = {
320
- auth: {
321
- backend: [
322
- "src/main/java/com/apptemplate/api/controller/AuthController.java"
323
- ],
324
- frontend: []
325
- },
326
- userManagement: {
327
- backend: [
328
- "src/main/java/com/apptemplate/api/controller/UserController.java"
329
- ],
330
- frontend: []
331
- },
332
- departments: {
333
- backend: [
334
- "src/main/java/com/apptemplate/api/controller/DepartmentController.java"
335
- ],
336
- frontend: []
337
- },
338
- fileUpload: {
339
- backend: [
340
- "src/main/java/com/apptemplate/api/controller/FileController.java"
341
- ],
342
- frontend: []
343
- },
344
- auditLogs: {
345
- backend: [
346
- "src/main/java/com/apptemplate/api/audit"
347
- ],
348
- frontend: []
349
- },
350
- notifications: {
351
- backend: [
352
- "src/main/java/com/apptemplate/api/controller/NotificationController.java"
353
- ],
354
- frontend: []
355
- },
356
- dataExport: {
357
- backend: [
358
- "src/main/java/com/apptemplate/api/controller/ExportController.java"
359
- ],
360
- frontend: []
361
- },
362
- dashboard: {
363
- backend: [],
364
- frontend: []
365
- }
366
- };
367
- var SPRING_FEATURE_MAP = {
368
- auth: {
369
- backend: [
370
- "src/main/java/com/apptemplate/api/features/auth"
371
- ],
372
- frontend: []
373
- },
374
- userManagement: {
375
- backend: [
376
- "src/main/java/com/apptemplate/api/features/users"
377
- ],
378
- frontend: []
379
- },
380
- departments: {
381
- backend: [
382
- "src/main/java/com/apptemplate/api/features/departments"
383
- ],
384
- frontend: []
385
- },
386
- fileUpload: {
387
- backend: [
388
- "src/main/java/com/apptemplate/api/features/files"
389
- ],
390
- frontend: []
391
- },
392
- auditLogs: {
393
- backend: [
394
- "src/main/java/com/apptemplate/api/common/audit"
395
- ],
396
- frontend: []
397
- },
398
- notifications: {
399
- backend: [
400
- "src/main/java/com/apptemplate/api/features/notifications"
401
- ],
402
- frontend: []
403
- },
404
- dataExport: {
405
- backend: [
406
- "src/main/java/com/apptemplate/api/features/export"
407
- ],
408
- frontend: []
409
- },
410
- dashboard: {
411
- backend: [],
412
- frontend: []
413
- }
414
- };
415
- var NESTJS_CLEAN_MAP = {
416
- auth: {
417
- backend: [
418
- "src/modules/auth"
419
- ],
420
- frontend: []
421
- },
422
- userManagement: {
423
- backend: [
424
- "src/modules/user-management"
425
- ],
426
- frontend: []
427
- },
428
- departments: {
429
- backend: [
430
- "src/modules/department-management"
431
- ],
432
- frontend: []
433
- },
434
- fileUpload: {
435
- backend: [
436
- "src/modules/file-management"
437
- ],
438
- frontend: []
439
- },
440
- auditLogs: {
441
- backend: [
442
- "src/modules/audit-log"
443
- ],
444
- frontend: []
445
- },
446
- notifications: {
447
- backend: [
448
- "src/modules/notification"
449
- ],
450
- frontend: []
451
- },
452
- dataExport: {
453
- backend: [
454
- "src/modules/export"
455
- ],
456
- frontend: []
457
- },
458
- dashboard: {
459
- backend: [],
460
- frontend: []
461
- }
462
- };
463
- var NESTJS_NLAYER_MAP = {
464
- auth: {
465
- backend: [
466
- "src/auth"
467
- ],
468
- frontend: []
469
- },
470
- userManagement: {
471
- backend: [],
472
- frontend: []
473
- },
474
- departments: {
475
- backend: [
476
- "src/modules/departments"
477
- ],
478
- frontend: []
479
- },
480
- fileUpload: {
481
- backend: [
482
- "src/modules/files"
483
- ],
484
- frontend: []
485
- },
486
- auditLogs: {
487
- backend: [],
488
- frontend: []
489
- },
490
- notifications: {
491
- backend: [
492
- "src/modules/notifications"
493
- ],
494
- frontend: []
495
- },
496
- dataExport: {
497
- backend: [
498
- "src/modules/export"
499
- ],
500
- frontend: []
501
- },
502
- dashboard: {
503
- backend: [],
504
- frontend: []
505
- }
506
- };
507
- var NESTJS_FEATURE_MAP = {
508
- auth: {
509
- backend: [
510
- "src/features/auth"
511
- ],
512
- frontend: []
513
- },
514
- userManagement: {
515
- backend: [
516
- "src/features/users"
517
- ],
518
- frontend: []
519
- },
520
- departments: {
521
- backend: [
522
- "src/features/departments"
523
- ],
524
- frontend: []
525
- },
526
- fileUpload: {
527
- backend: [
528
- "src/features/files"
529
- ],
530
- frontend: []
531
- },
532
- auditLogs: {
533
- backend: [],
534
- frontend: []
535
- },
536
- notifications: {
537
- backend: [
538
- "src/features/notifications"
539
- ],
540
- frontend: []
541
- },
542
- dataExport: {
543
- backend: [
544
- "src/features/export"
545
- ],
546
- frontend: []
547
- },
548
- dashboard: {
549
- backend: [],
550
- frontend: []
551
- }
552
- };
553
- var VUE_FRONTEND_MAP = {
554
- auth: [
555
- "src/pages/login.vue",
556
- "src/pages/forgot-password.vue",
557
- "src/pages/reset-password.vue",
558
- "src/pages/profile.vue",
559
- "src/stores/auth.js",
560
- "src/services/authApi.js"
561
- ],
562
- userManagement: [
563
- "src/pages/users",
564
- "src/stores/user.js",
565
- "src/services/userApi.js"
566
- ],
567
- departments: [
568
- "src/pages/departments",
569
- "src/stores/department.js",
570
- "src/services/departmentApi.js"
571
- ],
572
- fileUpload: [
573
- "src/pages/files",
574
- "src/services/fileService.js"
575
- ],
576
- auditLogs: [
577
- "src/pages/audit-logs",
578
- "src/services/auditLogService.js"
579
- ],
580
- notifications: [
581
- "src/pages/notifications",
582
- "src/stores/notification.js",
583
- "src/stores/persistentNotification.js",
584
- "src/services/notificationApi.js"
585
- ],
586
- dataExport: [
587
- "src/services/exportService.js"
588
- ],
589
- dashboard: [
590
- "src/pages/dashboard.vue"
591
- ]
592
- };
593
- var REACT_FRONTEND_MAP = {
594
- auth: [
595
- "src/pages/Login.tsx",
596
- "src/pages/ForgotPassword.tsx",
597
- "src/pages/ResetPassword.tsx",
598
- "src/pages/Profile.tsx",
599
- "src/stores/authStore.ts",
600
- "src/services/authApi.ts"
601
- ],
602
- userManagement: [
603
- "src/pages/Users.tsx",
604
- "src/stores/userStore.ts",
605
- "src/services/userApi.ts"
606
- ],
607
- departments: [
608
- "src/pages/Departments.tsx",
609
- "src/stores/departmentStore.ts",
610
- "src/services/departmentApi.ts"
611
- ],
612
- fileUpload: [
613
- "src/pages/FilesPage.tsx",
614
- "src/services/fileService.ts"
615
- ],
616
- auditLogs: [
617
- "src/pages/AuditLogsPage.tsx",
618
- "src/pages/AuditLogs.tsx",
619
- "src/services/auditLogService.ts"
620
- ],
621
- notifications: [
622
- "src/pages/Notifications.tsx",
623
- "src/stores/notificationStore.ts",
624
- "src/stores/persistentNotificationStore.ts",
625
- "src/services/notificationApi.ts"
626
- ],
627
- dataExport: [
628
- "src/services/exportService.ts"
629
- ],
630
- dashboard: [
631
- "src/pages/Dashboard.tsx",
632
- "src/pages/Dashboard.scss"
633
- ]
634
- };
635
- function getBackendFileMap(backend, architecture) {
636
- const key = `${backend}-${architecture}`;
637
- switch (key) {
638
- case "dotnet-clean":
639
- return DOTNET_CLEAN_MAP;
640
- case "dotnet-nlayer":
641
- return DOTNET_NLAYER_MAP;
642
- case "dotnet-feature":
643
- return DOTNET_FEATURE_MAP;
644
- case "spring-clean":
645
- return SPRING_CLEAN_MAP;
646
- case "spring-nlayer":
647
- return SPRING_NLAYER_MAP;
648
- case "spring-feature":
649
- return SPRING_FEATURE_MAP;
650
- case "nestjs-clean":
651
- return NESTJS_CLEAN_MAP;
652
- case "nestjs-nlayer":
653
- return NESTJS_NLAYER_MAP;
654
- case "nestjs-feature":
655
- return NESTJS_FEATURE_MAP;
656
- default:
657
- return {};
658
- }
659
- }
660
- function getFrontendFileMap(frontendFramework) {
661
- return frontendFramework === "vue" ? VUE_FRONTEND_MAP : REACT_FRONTEND_MAP;
662
- }
663
- function getFeaturesLabel(features, allFeatures) {
664
- if (features.length === allFeatures.length) {
665
- return "All features";
666
- }
667
- if (features.length === 0) {
668
- return "None (bare template)";
669
- }
670
- return features.map((f) => FEATURE_OPTIONS.find((o) => o.value === f)?.label || f).join(", ");
671
- }
672
-
673
7
  // src/cli.ts
674
8
  var validProjectTypes = ["fullstack", "backend", "frontend"];
675
9
  var validBackends = ["dotnet", "spring", "nestjs"];
676
10
  var validArchitectures = ["clean", "nlayer", "feature"];
677
11
  var validFrontendFrameworks = ["vue", "react"];
678
12
  var validUILibraries = ["vuetify", "primevue", "primereact", "mui"];
679
- var validFeatures = [...ALL_FEATURES];
13
+ var validVariants = ["minimal", "full"];
680
14
  function parseArgs() {
681
15
  const args = process.argv.slice(2);
682
16
  const result = {};
@@ -763,20 +97,12 @@ function parseArgs() {
763
97
  i++;
764
98
  continue;
765
99
  }
766
- if (arg === "--features" || arg === "--feat") {
100
+ if (arg === "-V" || arg === "--variant") {
767
101
  const value = args[++i];
768
- if (value) {
769
- if (value === "all") {
770
- result.features = [...ALL_FEATURES];
771
- } else {
772
- const requested = value.split(",").map((s) => s.trim());
773
- const valid = requested.filter((f) => validFeatures.includes(f));
774
- const invalid = requested.filter((f) => !validFeatures.includes(f));
775
- if (invalid.length > 0) {
776
- console.warn(`Warning: Invalid features "${invalid.join(", ")}". Valid options: ${validFeatures.join(", ")}`);
777
- }
778
- result.features = applyFeatureDependencies(valid);
779
- }
102
+ if (isValidVariant(value)) {
103
+ result.variant = value;
104
+ } else {
105
+ console.warn(`Warning: Invalid variant "${value}". Valid options: ${validVariants.join(", ")}`);
780
106
  }
781
107
  i++;
782
108
  continue;
@@ -803,6 +129,9 @@ function isValidFrontendFramework(value) {
803
129
  function isValidUI(value) {
804
130
  return value !== void 0 && validUILibraries.includes(value);
805
131
  }
132
+ function isValidVariant(value) {
133
+ return value !== void 0 && validVariants.includes(value);
134
+ }
806
135
  function isValidProjectName(value) {
807
136
  if (!value) return false;
808
137
  const pattern = /^[A-Za-z][A-Za-z0-9]*(\.[A-Za-z][A-Za-z0-9]*)+$/;
@@ -961,25 +290,25 @@ async function runInteractivePrompts(cliArgs) {
961
290
  if (p.isCancel(result)) return result;
962
291
  ui = result;
963
292
  }
964
- let features = cliArgs.features || [...ALL_FEATURES];
965
- if (!cliArgs.features) {
966
- const result = await p.multiselect({
967
- message: "Which features would you like to include?",
968
- options: FEATURE_OPTIONS.map((opt) => ({
969
- value: opt.value,
970
- label: opt.label,
971
- hint: opt.hint
972
- })),
973
- initialValues: [...ALL_FEATURES],
974
- required: false
293
+ let variant = cliArgs.variant || "full";
294
+ if (!cliArgs.variant) {
295
+ const result = await p.select({
296
+ message: "Which template variant would you like?",
297
+ options: [
298
+ {
299
+ value: "full",
300
+ label: "Full",
301
+ hint: "User management, departments, dashboard, all features"
302
+ },
303
+ {
304
+ value: "minimal",
305
+ label: "Minimal",
306
+ hint: "Auth, files, audit logs, notifications (no user/dept management)"
307
+ }
308
+ ]
975
309
  });
976
310
  if (p.isCancel(result)) return result;
977
- features = applyFeatureDependencies(result);
978
- if (features.length < result.length) {
979
- const removed = result.filter((f) => !features.includes(f));
980
- const removedLabels = removed.map((f) => FEATURE_OPTIONS.find((o) => o.value === f)?.label || f);
981
- console.log(pc.yellow(` \u26A0 Auto-removed due to dependencies: ${removedLabels.join(", ")}`));
982
- }
311
+ variant = result;
983
312
  }
984
313
  let projectName = cliArgs.projectName;
985
314
  const needsNamespace = projectType !== "frontend" && (backend === "dotnet" || backend === "spring");
@@ -1033,7 +362,7 @@ async function runInteractivePrompts(cliArgs) {
1033
362
  summaryLines.push(`${pc.cyan("Frontend:")} ${getFrontendLabel(frontendFramework)}`);
1034
363
  summaryLines.push(`${pc.cyan("UI Library:")} ${getUILabel(ui)}`);
1035
364
  }
1036
- summaryLines.push(`${pc.cyan("Features:")} ${getFeaturesLabel(features, ALL_FEATURES)}`);
365
+ summaryLines.push(`${pc.cyan("Template:")} ${getVariantLabel(variant)}`);
1037
366
  if (needsNamespace && projectName) {
1038
367
  summaryLines.push(`${pc.cyan("Namespace:")} ${projectName}`);
1039
368
  }
@@ -1059,7 +388,7 @@ async function runInteractivePrompts(cliArgs) {
1059
388
  projectName,
1060
389
  installDeps,
1061
390
  placeInRoot,
1062
- features
391
+ variant
1063
392
  };
1064
393
  }
1065
394
  function toPascalCase(str) {
@@ -1097,6 +426,13 @@ function getUILabel(ui) {
1097
426
  };
1098
427
  return labels[ui];
1099
428
  }
429
+ function getVariantLabel(variant) {
430
+ const labels = {
431
+ full: "Full (all features)",
432
+ minimal: "Minimal (no user/dept management)"
433
+ };
434
+ return labels[variant];
435
+ }
1100
436
 
1101
437
  // src/generator.ts
1102
438
  import * as p2 from "@clack/prompts";
@@ -1108,7 +444,18 @@ import fs5 from "fs";
1108
444
  import degit from "degit";
1109
445
  import path2 from "path";
1110
446
  import fs2 from "fs";
1111
- async function downloadTemplate(repo, folder, destPath) {
447
+ async function downloadBackendTemplate(repo, backend, architecture, variant, destPath) {
448
+ const folder = `backend/${backend}/${architecture}-architecture/${variant}`;
449
+ const source = `${repo}/${folder}`;
450
+ const emitter = degit(source, {
451
+ cache: false,
452
+ force: true,
453
+ verbose: false
454
+ });
455
+ await emitter.clone(destPath);
456
+ }
457
+ async function downloadFrontendTemplate(repo, framework, ui, variant, destPath) {
458
+ const folder = `frontend/${framework}/${ui}/${variant}`;
1112
459
  const source = `${repo}/${folder}`;
1113
460
  const emitter = degit(source, {
1114
461
  cache: false,
@@ -1207,12 +554,16 @@ async function renameDotNetProject(projectPath, config, newDotName, newNamespace
1207
554
  }
1208
555
  if (!fs3.existsSync(backendDir)) return;
1209
556
  const folderMappings = [
557
+ // Clean architecture (multi-project layout)
1210
558
  ["src/Core/App.Template.Domain", `src/Core/${newDotName}.Domain`],
1211
559
  ["src/Core/App.Template.Application", `src/Core/${newDotName}.Application`],
1212
560
  ["src/Infrastructure/App.Template.Infrastructure", `src/Infrastructure/${newDotName}.Infrastructure`],
1213
561
  ["src/Presentation/App.Template.WebAPI", `src/Presentation/${newDotName}.WebAPI`],
1214
562
  ["tests/App.Template.Domain.Tests", `tests/${newDotName}.Domain.Tests`],
1215
- ["tests/App.Template.Application.Tests", `tests/${newDotName}.Application.Tests`]
563
+ ["tests/App.Template.Application.Tests", `tests/${newDotName}.Application.Tests`],
564
+ // Feature and NLayer architectures (single-project layout)
565
+ ["src/App.Template.Api", `src/${newDotName}.Api`],
566
+ ["tests/App.Template.Api.Tests", `tests/${newDotName}.Api.Tests`]
1216
567
  ];
1217
568
  for (const [oldFolder, newFolder] of folderMappings) {
1218
569
  const oldPath = path3.join(backendDir, oldFolder);
@@ -1234,6 +585,39 @@ async function renameDotNetProject(projectPath, config, newDotName, newNamespace
1234
585
  return content.replace(/App\.Template/g, newDotName).replace(/AppTemplate/g, newNamespace);
1235
586
  });
1236
587
  }
588
+ function renameJavaPackageDir(oldDir, newDir) {
589
+ if (!fs3.existsSync(oldDir)) return;
590
+ const resolvedOld = path3.resolve(oldDir);
591
+ const resolvedNew = path3.resolve(newDir);
592
+ if (resolvedOld === resolvedNew) return;
593
+ if (fs3.existsSync(resolvedNew)) {
594
+ for (const entry of fs3.readdirSync(resolvedOld, { withFileTypes: true })) {
595
+ fs3.renameSync(
596
+ path3.join(resolvedOld, entry.name),
597
+ path3.join(resolvedNew, entry.name)
598
+ );
599
+ }
600
+ fs3.rmdirSync(resolvedOld);
601
+ } else {
602
+ fs3.mkdirSync(path3.dirname(resolvedNew), { recursive: true });
603
+ fs3.renameSync(resolvedOld, resolvedNew);
604
+ }
605
+ let current = path3.dirname(resolvedOld);
606
+ while (path3.basename(current) !== "java") {
607
+ if (!fs3.existsSync(current)) break;
608
+ try {
609
+ const remaining = fs3.readdirSync(current);
610
+ if (remaining.length === 0) {
611
+ fs3.rmdirSync(current);
612
+ current = path3.dirname(current);
613
+ } else {
614
+ break;
615
+ }
616
+ } catch {
617
+ break;
618
+ }
619
+ }
620
+ }
1237
621
  async function renameSpringProject(projectPath, config, newDotName) {
1238
622
  let backendDir;
1239
623
  if (config.projectType === "fullstack") {
@@ -1246,15 +630,43 @@ async function renameSpringProject(projectPath, config, newDotName) {
1246
630
  if (!fs3.existsSync(backendDir)) return;
1247
631
  const packageName = newDotName.toLowerCase().replace(/\./g, ".");
1248
632
  const artifactId = newDotName.toLowerCase().replace(/\./g, "-");
1249
- const pomPath = path3.join(backendDir, "pom.xml");
1250
- if (fs3.existsSync(pomPath)) {
1251
- let content = fs3.readFileSync(pomPath, "utf-8");
1252
- content = content.replace(/<groupId>com\.apptemplate<\/groupId>/g, `<groupId>${packageName}</groupId>`).replace(/<artifactId>apptemplate<\/artifactId>/g, `<artifactId>${artifactId}</artifactId>`).replace(/com\.apptemplate/g, packageName);
1253
- fs3.writeFileSync(pomPath, content);
1254
- }
633
+ const pkgPath = newDotName.toLowerCase().replace(/\./g, "/");
634
+ const compactName = newDotName.replace(/\./g, "").toLowerCase();
635
+ const displayName = newDotName.replace(/\./g, "");
636
+ const isClean = config.architecture === "clean";
637
+ await updateFileContents(backendDir, [".xml"], (content) => {
638
+ return content.replace(/<artifactId>app-template<\/artifactId>/g, `<artifactId>${artifactId}</artifactId>`).replace(/com\.apptemplate/g, packageName).replace(/AppTemplate/g, displayName);
639
+ });
640
+ await updateFileContents(backendDir, [".yml", ".yaml", ".properties"], (content) => {
641
+ if (isClean) {
642
+ return content.replace(/AppTemplate/g, displayName).replace(/apptemplate\.local/g, `${compactName}.local`).replace(/apptemplate_(\w+)/g, `${compactName}_$1`).replace(/\bapptemplate\b/g, packageName);
643
+ } else {
644
+ return content.replace(/AppTemplate/g, displayName).replace(/com\.apptemplate/g, packageName);
645
+ }
646
+ });
1255
647
  await updateFileContents(backendDir, [".java"], (content) => {
1256
- return content.replace(/com\.apptemplate/g, packageName);
648
+ if (isClean) {
649
+ return content.replace(/apptemplate\.local/g, `${compactName}.local`).replace(/\bapptemplate\b/g, packageName);
650
+ } else {
651
+ return content.replace(/com\.apptemplate/g, packageName);
652
+ }
1257
653
  });
654
+ if (isClean) {
655
+ const modules = ["api", "application", "domain", "infrastructure"];
656
+ for (const module of modules) {
657
+ for (const sourceRoot of ["src/main/java", "src/test/java"]) {
658
+ const oldDir = path3.join(backendDir, module, sourceRoot, "apptemplate");
659
+ const newDir = path3.join(backendDir, module, sourceRoot, pkgPath);
660
+ renameJavaPackageDir(oldDir, newDir);
661
+ }
662
+ }
663
+ } else {
664
+ for (const sourceRoot of ["src/main/java", "src/test/java"]) {
665
+ const oldDir = path3.join(backendDir, sourceRoot, "com", "apptemplate");
666
+ const newDir = path3.join(backendDir, sourceRoot, ...packageName.split("."));
667
+ renameJavaPackageDir(oldDir, newDir);
668
+ }
669
+ }
1258
670
  }
1259
671
  async function updateCommonFiles(projectPath, _config, newDotName, newNamespace) {
1260
672
  const filesToUpdate = [
@@ -1309,7 +721,7 @@ function getAllFiles(dir) {
1309
721
  for (const entry of entries) {
1310
722
  const fullPath = path3.join(dir, entry.name);
1311
723
  if (entry.isDirectory()) {
1312
- if (!["node_modules", ".git", "bin", "obj", "dist", "build"].includes(entry.name)) {
724
+ if (!["node_modules", ".git", "bin", "obj", "dist", "build", "target"].includes(entry.name)) {
1313
725
  files.push(...getAllFiles(fullPath));
1314
726
  }
1315
727
  } else {
@@ -1436,8 +848,6 @@ async function generateProject(config) {
1436
848
  spinner2.start("Downloading templates...");
1437
849
  try {
1438
850
  if (config.projectType !== "frontend") {
1439
- const archSuffix = config.architecture === "clean" ? "" : `-${config.architecture}`;
1440
- const sourceFolder = `backend-${config.backend}${archSuffix}`;
1441
851
  let destFolder;
1442
852
  if (config.projectType === "fullstack") {
1443
853
  destFolder = "backend";
@@ -1445,11 +855,10 @@ async function generateProject(config) {
1445
855
  destFolder = config.placeInRoot ? "" : "backend";
1446
856
  }
1447
857
  const destPath = destFolder ? path5.join(absolutePath, destFolder) : absolutePath;
1448
- await downloadTemplate(REPO, sourceFolder, destPath);
1449
- spinner2.message(`Downloaded ${sourceFolder}`);
858
+ await downloadBackendTemplate(REPO, config.backend, config.architecture, config.variant, destPath);
859
+ spinner2.message(`Downloaded backend-${config.backend}-${config.architecture}-${config.variant}`);
1450
860
  }
1451
861
  if (config.projectType !== "backend") {
1452
- const sourceFolder = `frontend-${config.ui}`;
1453
862
  let destFolder;
1454
863
  if (config.projectType === "fullstack") {
1455
864
  destFolder = "frontend";
@@ -1457,8 +866,8 @@ async function generateProject(config) {
1457
866
  destFolder = config.placeInRoot ? "" : "frontend";
1458
867
  }
1459
868
  const destPath = destFolder ? path5.join(absolutePath, destFolder) : absolutePath;
1460
- await downloadTemplate(REPO, sourceFolder, destPath);
1461
- spinner2.message(`Downloaded ${sourceFolder}`);
869
+ await downloadFrontendTemplate(REPO, config.frontendFramework, config.ui, config.variant, destPath);
870
+ spinner2.message(`Downloaded frontend-${config.frontendFramework}-${config.ui}-${config.variant}`);
1462
871
  }
1463
872
  await copyRootFiles(REPO, absolutePath, config);
1464
873
  spinner2.message("Downloaded configuration files");
@@ -1467,16 +876,6 @@ async function generateProject(config) {
1467
876
  spinner2.stop("Download failed");
1468
877
  throw error;
1469
878
  }
1470
- const deselectedFeatures = ALL_FEATURES.filter((f) => !config.features.includes(f));
1471
- if (deselectedFeatures.length > 0) {
1472
- spinner2.start("Removing deselected feature files...");
1473
- try {
1474
- await removeDeselectedFeatures(absolutePath, config, deselectedFeatures);
1475
- spinner2.stop(`Removed ${deselectedFeatures.length} deselected feature(s)`);
1476
- } catch (error) {
1477
- spinner2.stop("Feature removal had warnings");
1478
- }
1479
- }
1480
879
  spinner2.start("Updating configuration files...");
1481
880
  try {
1482
881
  await updateFolderReferences(absolutePath, config);
@@ -1521,48 +920,6 @@ async function generateProject(config) {
1521
920
  await createAppSettingsFromExample(absolutePath, config);
1522
921
  }
1523
922
  }
1524
- async function removeDeselectedFeatures(projectPath, config, deselectedFeatures) {
1525
- if (config.projectType !== "frontend") {
1526
- const backendMap = getBackendFileMap(config.backend, config.architecture);
1527
- let backendDir;
1528
- if (config.projectType === "fullstack") {
1529
- backendDir = path5.join(projectPath, "backend");
1530
- } else {
1531
- backendDir = config.placeInRoot ? projectPath : path5.join(projectPath, "backend");
1532
- }
1533
- for (const feature of deselectedFeatures) {
1534
- const featureMap = backendMap[feature];
1535
- if (featureMap) {
1536
- for (const filePath of featureMap.backend) {
1537
- const fullPath = path5.join(backendDir, filePath);
1538
- if (fs5.existsSync(fullPath)) {
1539
- fs5.rmSync(fullPath, { recursive: true, force: true });
1540
- }
1541
- }
1542
- }
1543
- }
1544
- }
1545
- if (config.projectType !== "backend") {
1546
- const frontendMap = getFrontendFileMap(config.frontendFramework);
1547
- let frontendDir;
1548
- if (config.projectType === "fullstack") {
1549
- frontendDir = path5.join(projectPath, "frontend");
1550
- } else {
1551
- frontendDir = config.placeInRoot ? projectPath : path5.join(projectPath, "frontend");
1552
- }
1553
- for (const feature of deselectedFeatures) {
1554
- const featurePaths = frontendMap[feature];
1555
- if (featurePaths) {
1556
- for (const filePath of featurePaths) {
1557
- const fullPath = path5.join(frontendDir, filePath);
1558
- if (fs5.existsSync(fullPath)) {
1559
- fs5.rmSync(fullPath, { recursive: true, force: true });
1560
- }
1561
- }
1562
- }
1563
- }
1564
- }
1565
- }
1566
923
  function cleanupFullstackDockerFiles(projectPath) {
1567
924
  const filesToDelete = [
1568
925
  "backend/Dockerfile",
@@ -1727,7 +1084,7 @@ async function main() {
1727
1084
  projectName: cliArgs.projectName,
1728
1085
  installDeps: cliArgs.install || false,
1729
1086
  placeInRoot: cliArgs.root || false,
1730
- features: cliArgs.features || [...ALL_FEATURES]
1087
+ variant: cliArgs.variant || "full"
1731
1088
  };
1732
1089
  } else {
1733
1090
  const result = await runInteractivePrompts(cliArgs);
@@ -1763,8 +1120,9 @@ ${pc3.bold("Options:")}
1763
1120
  ${pc3.yellow("-u, --ui")} UI library: vuetify, primevue (Vue) | mui, primereact (React)
1764
1121
  ${pc3.yellow("-n, --name")} Project namespace (Company.Project format, .NET/Spring only)
1765
1122
  ${pc3.yellow("-r, --root")} Place files in project root ${pc3.gray("(backend/frontend-only)")}
1766
- ${pc3.yellow("--features")} Features to include (comma-separated or "all")
1767
- Options: auth,userManagement,departments,fileUpload,auditLogs,notifications,dataExport,dashboard
1123
+ ${pc3.yellow("-V, --variant")} Template variant: full, minimal ${pc3.gray("(default: full)")}
1124
+ full: All features (user management, departments, dashboard)
1125
+ minimal: Auth, files, audit logs, notifications only
1768
1126
  ${pc3.yellow("-i, --install")} Install dependencies after creation
1769
1127
  ${pc3.yellow("-h, --help")} Show this help message
1770
1128
  ${pc3.yellow("-v, --version")} Show version number
@@ -1776,8 +1134,8 @@ ${pc3.bold("Examples:")}
1776
1134
  ${pc3.gray("# Create fullstack project with .NET backend")}
1777
1135
  npm create apptemplate@latest my-app -b dotnet -n "MyCompany.MyApp" -i
1778
1136
 
1779
- ${pc3.gray("# Create backend-only project with Spring Boot")}
1780
- npm create apptemplate@latest my-api -t backend -b spring -n "MyCompany.MyApi"
1137
+ ${pc3.gray("# Create minimal backend-only project (no user management)")}
1138
+ npm create apptemplate@latest my-api -t backend -b spring -n "MyCompany.MyApi" -V minimal
1781
1139
 
1782
1140
  ${pc3.gray("# Create frontend-only project with PrimeVue")}
1783
1141
  npm create apptemplate@latest my-spa -t frontend -u primevue