create-ng-tailwind 3.1.0 → 4.0.0

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +81 -350
  2. package/README.md +93 -157
  3. package/lib/cli/index.js +29 -3
  4. package/lib/cli/interactive.js +26 -1
  5. package/lib/managers/ProjectManager.js +0 -4
  6. package/lib/templates/base/components.js +243 -0
  7. package/lib/templates/base/index.js +207 -0
  8. package/lib/templates/base/infrastructure.js +314 -0
  9. package/lib/templates/base/linting.js +359 -0
  10. package/lib/templates/base/pwa.js +103 -0
  11. package/lib/templates/base/services.js +362 -0
  12. package/lib/templates/blog/app.js +250 -0
  13. package/lib/templates/blog/components.js +360 -0
  14. package/lib/templates/blog/i18n.js +77 -0
  15. package/lib/templates/blog/index.js +126 -0
  16. package/lib/templates/blog/pages.js +554 -0
  17. package/lib/templates/blog/services.js +390 -0
  18. package/lib/templates/dashboard/app.js +320 -0
  19. package/lib/templates/dashboard/charts.js +305 -0
  20. package/lib/templates/dashboard/components.js +410 -0
  21. package/lib/templates/dashboard/i18n.js +340 -0
  22. package/lib/templates/dashboard/index.js +141 -0
  23. package/lib/templates/dashboard/layout.js +310 -0
  24. package/lib/templates/dashboard/pages.js +681 -0
  25. package/lib/templates/ecommerce/app.js +315 -0
  26. package/lib/templates/ecommerce/components.js +496 -0
  27. package/lib/templates/ecommerce/i18n.js +389 -0
  28. package/lib/templates/ecommerce/index.js +152 -0
  29. package/lib/templates/ecommerce/layout.js +270 -0
  30. package/lib/templates/ecommerce/pages.js +969 -0
  31. package/lib/templates/ecommerce/services.js +300 -0
  32. package/lib/templates/index.js +12 -0
  33. package/lib/templates/landing/index.js +1117 -0
  34. package/lib/templates/portfolio/index.js +1160 -0
  35. package/lib/templates/saas/index.js +1371 -0
  36. package/lib/templates/starter/app.js +364 -0
  37. package/lib/templates/starter/i18n.js +856 -0
  38. package/lib/templates/starter/index.js +52 -4060
  39. package/lib/templates/starter/layout.js +852 -0
  40. package/lib/templates/starter/pages.js +1241 -0
  41. package/package.json +1 -1
  42. package/lib/templates/starter/features.js +0 -867
  43. package/lib/utils/ai-config.js +0 -641
  44. /package/lib/templates/{starter → base}/advanced-features.js +0 -0
  45. /package/lib/templates/{starter → base}/seo-assets.js +0 -0
  46. /package/lib/templates/{starter → base}/seo-features.js +0 -0
  47. /package/lib/templates/{starter → base}/ui-features.js +0 -0
@@ -0,0 +1,207 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ // Import modular components
5
+ const { createCoreServices } = require('./services');
6
+ const { createSharedComponents } = require('./components');
7
+ const {
8
+ createDirectoryStructure,
9
+ createEnvironments,
10
+ createGuards,
11
+ createModels,
12
+ createPipes,
13
+ } = require('./infrastructure');
14
+ const {
15
+ createVSCodeSettings,
16
+ updateTsConfig,
17
+ setupLinting,
18
+ formatCode,
19
+ } = require('./linting');
20
+ const { setupPWA } = require('./pwa');
21
+
22
+ // Import existing modular components
23
+ const {
24
+ createAuthInterceptor,
25
+ createErrorInterceptor,
26
+ createLoadingInterceptor,
27
+ createCachingInterceptor,
28
+ createToastService,
29
+ createToastComponent,
30
+ createLoadingService,
31
+ createCacheService,
32
+ } = require('./advanced-features');
33
+
34
+ const {
35
+ createModalService,
36
+ createModalComponent,
37
+ createTruncatePipe,
38
+ createClickOutsideDirective,
39
+ createTooltipDirective,
40
+ } = require('./ui-features');
41
+
42
+ const { createSEOService, createStructuredDataUtil, createRobotsTxt } = require('./seo-features');
43
+
44
+ const {
45
+ createDefaultOGImage,
46
+ createFavicon,
47
+ createAppleTouchIcon,
48
+ createAndroidIcon,
49
+ createAndroidIconLarge,
50
+ createLogo,
51
+ } = require('./seo-assets');
52
+
53
+ /**
54
+ * Base Template
55
+ * Contains all core infrastructure: services, guards, interceptors, pipes, directives, etc.
56
+ * UI templates (starter, dashboard, ecommerce) extend this base.
57
+ */
58
+ const base = {
59
+ info: {
60
+ name: 'Base',
61
+ description: 'Core infrastructure template with services, guards, interceptors, and shared components',
62
+ features: [
63
+ 'Modern standalone Angular 20+ architecture with signals',
64
+ 'HTTP Interceptors (Auth, Error, Loading, Caching)',
65
+ 'Core services (API, Auth, Storage, Cache, Loading, SEO)',
66
+ 'Toast/Notification system with auto-dismiss',
67
+ 'Modal/Dialog system with confirm & alert',
68
+ 'Essential UI components (Button, Card, Spinner, Toast, Modal)',
69
+ 'Truncate pipe for text truncation',
70
+ 'Useful Directives (ClickOutside, Tooltip)',
71
+ 'TypeScript interfaces and models',
72
+ 'ESLint + Prettier + simple-git-hooks (pre-commit)',
73
+ 'PWA-ready with service worker configuration',
74
+ 'Icons library (@ng-icons/heroicons)',
75
+ ],
76
+ },
77
+
78
+ async apply(config, spinner) {
79
+ this.spinner = spinner;
80
+ const chalk = require('chalk');
81
+
82
+ const completeStep = (message) => {
83
+ if (spinner) {
84
+ spinner.stop();
85
+ console.log(chalk.green(' ✔') + chalk.white(' ' + message));
86
+ spinner.start();
87
+ }
88
+ };
89
+
90
+ // Create core infrastructure
91
+ if (spinner) spinner.update('Setting up base project structure...');
92
+ await createDirectoryStructure(config);
93
+ await createEnvironments(config);
94
+ await createCoreServices(config);
95
+ await this.createSEOFeatures(config);
96
+ await this.createHttpInterceptors(config);
97
+ await createGuards(config);
98
+ await createSharedComponents(config);
99
+ await createLoadingService(config);
100
+ await createCacheService(config);
101
+ await this.createToastSystem(config);
102
+ await this.createModalSystem(config);
103
+ await createPipes(config);
104
+ await this.createAdditionalPipes(config);
105
+ await this.createDirectives(config);
106
+ await createModels(config);
107
+ await createVSCodeSettings(config);
108
+ await updateTsConfig(config);
109
+ await setupLinting(config);
110
+ await setupPWA(config);
111
+
112
+ if (spinner) spinner.stop();
113
+
114
+ // Show base template summary
115
+ console.log('');
116
+ completeStep('Core infrastructure configured');
117
+ completeStep('HTTP interceptors (Auth, Error, Loading, Cache)');
118
+ completeStep('Core services (9 services including SEO)');
119
+ completeStep('UI components (Button, Card, Toast, Modal, Spinner)');
120
+ completeStep('Utility pipes & directives');
121
+ completeStep('PWA support configured');
122
+ completeStep('ESLint + Prettier + simple-git-hooks');
123
+ },
124
+
125
+ async createSEOFeatures(config) {
126
+ // Create SEO Service
127
+ const seoService = createSEOService();
128
+ await fs.writeFile(
129
+ path.join(config.fullPath, 'src/app/core/services/seo.service.ts'),
130
+ seoService
131
+ );
132
+
133
+ // Create Structured Data Utility
134
+ const structuredDataUtil = createStructuredDataUtil();
135
+ await fs.writeFile(
136
+ path.join(config.fullPath, 'src/app/core/utils/structured-data.ts'),
137
+ structuredDataUtil
138
+ );
139
+
140
+ // Create robots.txt in public folder
141
+ const robotsTxt = createRobotsTxt();
142
+ await fs.writeFile(path.join(config.fullPath, 'public/robots.txt'), robotsTxt);
143
+
144
+ // Create default OG image (SVG as placeholder)
145
+ const ogImage = createDefaultOGImage(config.projectName);
146
+ await fs.writeFile(path.join(config.fullPath, 'public/assets/images/og-default.svg'), ogImage);
147
+
148
+ // Create favicon
149
+ const favicon = createFavicon(config.projectName);
150
+ await fs.writeFile(path.join(config.fullPath, 'public/favicon.svg'), favicon);
151
+
152
+ // Create Apple Touch Icon (SVG as placeholder)
153
+ const appleTouchIcon = createAppleTouchIcon(config.projectName);
154
+ await fs.writeFile(
155
+ path.join(config.fullPath, 'public/assets/icons/apple-touch-icon.svg'),
156
+ appleTouchIcon
157
+ );
158
+
159
+ // Create Android Chrome icons (SVG as placeholder)
160
+ const androidIcon = createAndroidIcon(config.projectName);
161
+ await fs.writeFile(
162
+ path.join(config.fullPath, 'public/assets/icons/android-chrome-192x192.svg'),
163
+ androidIcon
164
+ );
165
+
166
+ const androidIconLarge = createAndroidIconLarge(config.projectName);
167
+ await fs.writeFile(
168
+ path.join(config.fullPath, 'public/assets/icons/android-chrome-512x512.svg'),
169
+ androidIconLarge
170
+ );
171
+
172
+ // Create logo
173
+ const logo = createLogo(config.projectName);
174
+ await fs.writeFile(path.join(config.fullPath, 'public/assets/images/logo.svg'), logo);
175
+ },
176
+
177
+ async createHttpInterceptors(config) {
178
+ await createAuthInterceptor(config);
179
+ await createErrorInterceptor(config);
180
+ await createLoadingInterceptor(config);
181
+ await createCachingInterceptor(config);
182
+ },
183
+
184
+ async createToastSystem(config) {
185
+ await createToastService(config);
186
+ await createToastComponent(config);
187
+ },
188
+
189
+ async createModalSystem(config) {
190
+ await createModalService(config);
191
+ await createModalComponent(config);
192
+ },
193
+
194
+ async createAdditionalPipes(config) {
195
+ await createTruncatePipe(config);
196
+ },
197
+
198
+ async createDirectives(config) {
199
+ await createClickOutsideDirective(config);
200
+ await createTooltipDirective(config);
201
+ },
202
+
203
+ // Expose formatCode for child templates to use
204
+ formatCode,
205
+ };
206
+
207
+ module.exports = base;
@@ -0,0 +1,314 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Create directory structure for base template
6
+ */
7
+ async function createDirectoryStructure(config) {
8
+ const directories = [
9
+ 'src/environments',
10
+ 'src/app/core/services',
11
+ 'src/app/core/guards',
12
+ 'src/app/core/interceptors',
13
+ 'src/app/core/utils',
14
+ 'src/app/shared/components/button',
15
+ 'src/app/shared/components/card',
16
+ 'src/app/shared/components/loading-spinner',
17
+ 'src/app/shared/components/toast',
18
+ 'src/app/shared/components/modal',
19
+ 'src/app/shared/pipes',
20
+ 'src/app/shared/directives',
21
+ 'src/app/shared/models',
22
+ 'public/assets/images',
23
+ 'public/assets/icons',
24
+ ];
25
+
26
+ for (const dir of directories) {
27
+ await fs.ensureDir(path.join(config.fullPath, dir));
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Create environment files
33
+ */
34
+ async function createEnvironments(config) {
35
+ // Development environment
36
+ const devEnvironment = `export const environment = {
37
+ production: false,
38
+ apiUrl: 'http://localhost:3000/api',
39
+ appName: '${config.projectName}',
40
+ version: '1.0.0',
41
+ enableDevTools: true,
42
+ logLevel: 'debug' as 'debug' | 'info' | 'warn' | 'error',
43
+ features: {
44
+ enableAnalytics: false,
45
+ enableLogging: true,
46
+ enableMocking: true
47
+ },
48
+ auth: {
49
+ tokenKey: 'auth_token',
50
+ refreshTokenKey: 'refresh_token',
51
+ tokenExpirationBuffer: 300000 // 5 minutes
52
+ },
53
+ api: {
54
+ timeout: 30000,
55
+ retryAttempts: 3
56
+ }
57
+ };`;
58
+
59
+ await fs.writeFile(
60
+ path.join(config.fullPath, 'src/environments/environment.ts'),
61
+ devEnvironment
62
+ );
63
+
64
+ // Production environment
65
+ const prodEnvironment = `export const environment = {
66
+ production: true,
67
+ apiUrl: 'https://api.${config.projectName}.com/api',
68
+ appName: '${config.projectName}',
69
+ version: '1.0.0',
70
+ enableDevTools: false,
71
+ logLevel: 'error' as 'debug' | 'info' | 'warn' | 'error',
72
+ features: {
73
+ enableAnalytics: true,
74
+ enableLogging: false,
75
+ enableMocking: false
76
+ },
77
+ auth: {
78
+ tokenKey: 'auth_token',
79
+ refreshTokenKey: 'refresh_token',
80
+ tokenExpirationBuffer: 300000 // 5 minutes
81
+ },
82
+ api: {
83
+ timeout: 30000,
84
+ retryAttempts: 1
85
+ }
86
+ };`;
87
+
88
+ await fs.writeFile(
89
+ path.join(config.fullPath, 'src/environments/environment.prod.ts'),
90
+ prodEnvironment
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Create auth guard
96
+ */
97
+ async function createGuards(config) {
98
+ const authGuard = `import { CanActivateFn, Router } from '@angular/router';
99
+ import { inject } from '@angular/core';
100
+
101
+ /**
102
+ * Simple authentication guard
103
+ * Replace with your actual authentication logic
104
+ */
105
+ export const authGuard: CanActivateFn = (_route, _state) => {
106
+ const router = inject(Router);
107
+
108
+ // Replace with your actual authentication check
109
+ const isAuthenticated = localStorage.getItem('auth-token') !== null;
110
+
111
+ if (!isAuthenticated) {
112
+ // Redirect to login page
113
+ router.navigate(['/auth/login']);
114
+ return false;
115
+ }
116
+
117
+ return true;
118
+ };
119
+
120
+ /**
121
+ * Example: Check if user has specific role
122
+ */
123
+ export const roleGuard = (requiredRole: string): CanActivateFn => {
124
+ return (_route, _state) => {
125
+ const router = inject(Router);
126
+
127
+ // Replace with your actual role checking logic
128
+ const userRole = localStorage.getItem('user-role');
129
+
130
+ if (userRole !== requiredRole) {
131
+ router.navigate(['/unauthorized']);
132
+ return false;
133
+ }
134
+
135
+ return true;
136
+ };
137
+ };`;
138
+
139
+ await fs.writeFile(path.join(config.fullPath, 'src/app/core/guards/auth.guard.ts'), authGuard);
140
+ }
141
+
142
+ /**
143
+ * Create model interfaces
144
+ */
145
+ async function createModels(config) {
146
+ // User Interface
147
+ const userInterface = `export interface User {
148
+ id: string;
149
+ email: string;
150
+ firstName: string;
151
+ lastName: string;
152
+ avatar?: string;
153
+ role: 'user' | 'admin' | 'moderator';
154
+ createdAt: Date;
155
+ updatedAt: Date;
156
+ isActive: boolean;
157
+ }
158
+
159
+ export interface UserProfile {
160
+ user: User;
161
+ preferences: {
162
+ notifications: boolean;
163
+ language: string;
164
+ };
165
+ }
166
+
167
+ export interface CreateUserRequest {
168
+ email: string;
169
+ password: string;
170
+ firstName: string;
171
+ lastName: string;
172
+ }
173
+
174
+ export interface UpdateUserRequest {
175
+ firstName?: string;
176
+ lastName?: string;
177
+ avatar?: string;
178
+ }`;
179
+
180
+ await fs.writeFile(
181
+ path.join(config.fullPath, 'src/app/shared/models/user.interface.ts'),
182
+ userInterface
183
+ );
184
+
185
+ // API Response Interface
186
+ const apiResponseInterface = `export interface ApiResponse<T = unknown> {
187
+ data: T;
188
+ message: string;
189
+ success: boolean;
190
+ errors?: string[];
191
+ meta?: {
192
+ total: number;
193
+ page: number;
194
+ perPage: number;
195
+ totalPages: number;
196
+ };
197
+ }
198
+
199
+ export interface ApiError {
200
+ message: string;
201
+ code: string;
202
+ status: number;
203
+ timestamp: string;
204
+ details?: Record<string, string[]>;
205
+ }
206
+
207
+ export interface PaginatedResponse<T> {
208
+ data: T[];
209
+ meta: {
210
+ total: number;
211
+ page: number;
212
+ perPage: number;
213
+ totalPages: number;
214
+ hasNextPage: boolean;
215
+ hasPrevPage: boolean;
216
+ };
217
+ }`;
218
+
219
+ await fs.writeFile(
220
+ path.join(config.fullPath, 'src/app/shared/models/api-response.interface.ts'),
221
+ apiResponseInterface
222
+ );
223
+ }
224
+
225
+ /**
226
+ * Create pipes (Truncate, TimeAgo)
227
+ */
228
+ async function createPipes(config) {
229
+ // Truncate Pipe
230
+ const truncatePipe = `import { Pipe, PipeTransform } from '@angular/core';
231
+
232
+ @Pipe({
233
+ name: 'truncate',
234
+ standalone: true
235
+ })
236
+ export class TruncatePipe implements PipeTransform {
237
+ transform(value: string | null | undefined, limit = 50, suffix = '...'): string {
238
+ if (!value) return '';
239
+
240
+ if (value.length <= limit) {
241
+ return value;
242
+ }
243
+
244
+ return value.substring(0, limit).trim() + suffix;
245
+ }
246
+ }`;
247
+
248
+ await fs.writeFile(
249
+ path.join(config.fullPath, 'src/app/shared/pipes/truncate.pipe.ts'),
250
+ truncatePipe
251
+ );
252
+
253
+ // Time Ago Pipe
254
+ const timeAgoPipe = `import { Pipe, PipeTransform } from '@angular/core';
255
+
256
+ @Pipe({
257
+ name: 'timeAgo',
258
+ standalone: true
259
+ })
260
+ export class TimeAgoPipe implements PipeTransform {
261
+ transform(value: string | number | Date | null | undefined): string {
262
+ if (!value) return '';
263
+
264
+ const date = new Date(value);
265
+ const now = new Date();
266
+ const secondsAgo = Math.floor((now.getTime() - date.getTime()) / 1000);
267
+
268
+ if (secondsAgo < 60) {
269
+ return 'just now';
270
+ }
271
+
272
+ const minutesAgo = Math.floor(secondsAgo / 60);
273
+ if (minutesAgo < 60) {
274
+ return \`\${minutesAgo} minute\${minutesAgo === 1 ? '' : 's'} ago\`;
275
+ }
276
+
277
+ const hoursAgo = Math.floor(minutesAgo / 60);
278
+ if (hoursAgo < 24) {
279
+ return \`\${hoursAgo} hour\${hoursAgo === 1 ? '' : 's'} ago\`;
280
+ }
281
+
282
+ const daysAgo = Math.floor(hoursAgo / 24);
283
+ if (daysAgo < 7) {
284
+ return \`\${daysAgo} day\${daysAgo === 1 ? '' : 's'} ago\`;
285
+ }
286
+
287
+ const weeksAgo = Math.floor(daysAgo / 7);
288
+ if (weeksAgo < 4) {
289
+ return \`\${weeksAgo} week\${weeksAgo === 1 ? '' : 's'} ago\`;
290
+ }
291
+
292
+ const monthsAgo = Math.floor(daysAgo / 30);
293
+ if (monthsAgo < 12) {
294
+ return \`\${monthsAgo} month\${monthsAgo === 1 ? '' : 's'} ago\`;
295
+ }
296
+
297
+ const yearsAgo = Math.floor(daysAgo / 365);
298
+ return \`\${yearsAgo} year\${yearsAgo === 1 ? '' : 's'} ago\`;
299
+ }
300
+ }`;
301
+
302
+ await fs.writeFile(
303
+ path.join(config.fullPath, 'src/app/shared/pipes/time-ago.pipe.ts'),
304
+ timeAgoPipe
305
+ );
306
+ }
307
+
308
+ module.exports = {
309
+ createDirectoryStructure,
310
+ createEnvironments,
311
+ createGuards,
312
+ createModels,
313
+ createPipes,
314
+ };