create-ng-tailwind 3.1.0 → 4.1.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.

Potentially problematic release.


This version of create-ng-tailwind might be problematic. Click here for more details.

Files changed (48) hide show
  1. package/CHANGELOG.md +96 -341
  2. package/README.md +111 -157
  3. package/lib/cli/index.js +74 -3
  4. package/lib/cli/interactive.js +26 -1
  5. package/lib/managers/ProjectManager.js +2 -5
  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/lib/utils/nodeCompat.js +85 -0
  42. package/package.json +1 -1
  43. package/lib/templates/starter/features.js +0 -867
  44. package/lib/utils/ai-config.js +0 -641
  45. /package/lib/templates/{starter → base}/advanced-features.js +0 -0
  46. /package/lib/templates/{starter → base}/seo-assets.js +0 -0
  47. /package/lib/templates/{starter → base}/seo-features.js +0 -0
  48. /package/lib/templates/{starter → base}/ui-features.js +0 -0
@@ -0,0 +1,103 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Setup PWA (Progressive Web App) configuration
6
+ */
7
+ async function setupPWA(config) {
8
+ // Create manifest.json
9
+ const manifest = {
10
+ name: config.projectName,
11
+ short_name: config.projectName,
12
+ theme_color: '#3b82f6',
13
+ background_color: '#ffffff',
14
+ display: 'standalone',
15
+ scope: '/',
16
+ start_url: '/',
17
+ description: `${config.projectName} - Built with Angular and Tailwind CSS`,
18
+ icons: [
19
+ {
20
+ src: 'assets/icons/android-chrome-192x192.svg',
21
+ sizes: '192x192',
22
+ type: 'image/svg+xml',
23
+ purpose: 'maskable any',
24
+ },
25
+ {
26
+ src: 'assets/icons/android-chrome-512x512.svg',
27
+ sizes: '512x512',
28
+ type: 'image/svg+xml',
29
+ purpose: 'maskable any',
30
+ },
31
+ ],
32
+ };
33
+
34
+ // Write manifest to public directory (new Angular structure)
35
+ await fs.ensureDir(path.join(config.fullPath, 'public'));
36
+ await fs.writeFile(
37
+ path.join(config.fullPath, 'public/manifest.webmanifest'),
38
+ JSON.stringify(manifest, null, 2)
39
+ );
40
+
41
+ // Create ngsw-config.json (Service Worker configuration)
42
+ const ngswConfig = {
43
+ $schema: './node_modules/@angular/service-worker/config/schema.json',
44
+ index: '/index.html',
45
+ assetGroups: [
46
+ {
47
+ name: 'app',
48
+ installMode: 'prefetch',
49
+ resources: {
50
+ files: ['/favicon.ico', '/index.html', '/manifest.webmanifest', '/*.css', '/*.js'],
51
+ },
52
+ },
53
+ {
54
+ name: 'assets',
55
+ installMode: 'lazy',
56
+ updateMode: 'prefetch',
57
+ resources: {
58
+ files: [
59
+ '/assets/**',
60
+ '/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)',
61
+ ],
62
+ },
63
+ },
64
+ ],
65
+ dataGroups: [
66
+ {
67
+ name: 'api',
68
+ urls: ['https://api.example.com/**'],
69
+ cacheConfig: {
70
+ maxSize: 100,
71
+ maxAge: '1h',
72
+ timeout: '10s',
73
+ strategy: 'freshness',
74
+ },
75
+ },
76
+ ],
77
+ };
78
+
79
+ await fs.writeFile(
80
+ path.join(config.fullPath, 'ngsw-config.json'),
81
+ JSON.stringify(ngswConfig, null, 2)
82
+ );
83
+
84
+ // Update index.html to include PWA assets
85
+ const indexPath = path.join(config.fullPath, 'src/index.html');
86
+ let indexContent = await fs.readFile(indexPath, 'utf8');
87
+
88
+ // Add manifest link and theme color meta tag if not present
89
+ if (!indexContent.includes('manifest.webmanifest')) {
90
+ indexContent = indexContent.replace(
91
+ '</head>',
92
+ ` <link rel="manifest" href="manifest.webmanifest">
93
+ <meta name="theme-color" content="#3b82f6">
94
+ <link rel="apple-touch-icon" href="assets/icons/apple-touch-icon.svg">
95
+ </head>`
96
+ );
97
+ await fs.writeFile(indexPath, indexContent);
98
+ }
99
+ }
100
+
101
+ module.exports = {
102
+ setupPWA,
103
+ };
@@ -0,0 +1,362 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Create API Service
6
+ */
7
+ async function createApiService(config) {
8
+ const apiService = `import { Injectable, inject } from '@angular/core';
9
+ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
10
+ import { Observable, throwError } from 'rxjs';
11
+ import { catchError, retry } from 'rxjs/operators';
12
+
13
+ import { ApiResponse } from '@shared/models/api-response.interface';
14
+
15
+ @Injectable({
16
+ providedIn: 'root'
17
+ })
18
+ export class ApiService {
19
+ private http = inject(HttpClient);
20
+ private readonly baseUrl = 'https://api.example.com'; // Configure your API URL
21
+
22
+ /**
23
+ * Generic GET request
24
+ */
25
+ get<T>(endpoint: string): Observable<ApiResponse<T>> {
26
+ return this.http.get<ApiResponse<T>>(\`\${this.baseUrl}/\${endpoint}\`)
27
+ .pipe(
28
+ retry(1),
29
+ catchError(this.handleError)
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Generic POST request
35
+ */
36
+ post<T, D = unknown>(endpoint: string, data: D): Observable<ApiResponse<T>> {
37
+ return this.http.post<ApiResponse<T>>(\`\${this.baseUrl}/\${endpoint}\`, data)
38
+ .pipe(
39
+ catchError(this.handleError)
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Generic PUT request
45
+ */
46
+ put<T, D = unknown>(endpoint: string, data: D): Observable<ApiResponse<T>> {
47
+ return this.http.put<ApiResponse<T>>(\`\${this.baseUrl}/\${endpoint}\`, data)
48
+ .pipe(
49
+ catchError(this.handleError)
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Generic DELETE request
55
+ */
56
+ delete<T>(endpoint: string): Observable<ApiResponse<T>> {
57
+ return this.http.delete<ApiResponse<T>>(\`\${this.baseUrl}/\${endpoint}\`)
58
+ .pipe(
59
+ catchError(this.handleError)
60
+ );
61
+ }
62
+
63
+ /**
64
+ * Handle HTTP errors
65
+ */
66
+ private handleError(error: HttpErrorResponse) {
67
+ let errorMessage = 'An unknown error occurred';
68
+
69
+ if (error.error instanceof ErrorEvent) {
70
+ // Client-side error
71
+ errorMessage = error.error.message;
72
+ } else {
73
+ // Server-side error
74
+ errorMessage = error.error?.message || \`Server returned code: \${error.status}, error message is: \${error.message}\`;
75
+ }
76
+
77
+ console.error('API Error:', errorMessage);
78
+ return throwError(() => new Error(errorMessage));
79
+ }
80
+ }`;
81
+
82
+ await fs.writeFile(
83
+ path.join(config.fullPath, 'src/app/core/services/api.service.ts'),
84
+ apiService
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Create Auth Service
90
+ */
91
+ async function createAuthService(config) {
92
+ const authService = `import { Injectable, inject } from '@angular/core';
93
+ import { HttpClient } from '@angular/common/http';
94
+ import { Router } from '@angular/router';
95
+ import { BehaviorSubject, Observable, throwError } from 'rxjs';
96
+ import { catchError, tap } from 'rxjs/operators';
97
+ import { environment } from '@environments/environment';
98
+ import { StorageService } from './storage.service';
99
+
100
+ interface User {
101
+ id: string;
102
+ email: string;
103
+ firstName: string;
104
+ lastName: string;
105
+ }
106
+
107
+ interface AuthResponse {
108
+ token: string;
109
+ user: User;
110
+ refreshToken?: string;
111
+ }
112
+
113
+ interface LoginCredentials {
114
+ email: string;
115
+ password: string;
116
+ }
117
+
118
+ interface RegisterData {
119
+ email: string;
120
+ password: string;
121
+ firstName: string;
122
+ lastName: string;
123
+ }
124
+
125
+ @Injectable({
126
+ providedIn: 'root'
127
+ })
128
+ export class AuthService {
129
+ private http = inject(HttpClient);
130
+ private router = inject(Router);
131
+ private storage = inject(StorageService);
132
+
133
+ private readonly API_URL = environment.apiUrl;
134
+
135
+ // Reactive state
136
+ private _isAuthenticated$ = new BehaviorSubject<boolean>(this.hasValidToken());
137
+ private _currentUser$ = new BehaviorSubject<User | null>(this.getCurrentUserFromStorage());
138
+
139
+ // Public observables
140
+ public readonly isAuthenticated$ = this._isAuthenticated$.asObservable();
141
+ public readonly currentUser$ = this._currentUser$.asObservable();
142
+
143
+ constructor() {
144
+ // Initialize authentication state on service creation
145
+ this.checkAuthenticationStatus();
146
+ }
147
+
148
+ login(credentials: LoginCredentials): Observable<AuthResponse> {
149
+ // For demo purposes, simulate login
150
+ if (credentials.email === 'demo@example.com' && credentials.password === 'password') {
151
+ const mockResponse: AuthResponse = {
152
+ token: 'mock_jwt_token_12345',
153
+ user: {
154
+ id: '1',
155
+ email: 'demo@example.com',
156
+ firstName: 'Demo',
157
+ lastName: 'User'
158
+ }
159
+ };
160
+
161
+ // Store auth data
162
+ this.storage.setItem(environment.auth.tokenKey, mockResponse.token);
163
+ this.storage.setItem('current_user', JSON.stringify(mockResponse.user));
164
+
165
+ // Update reactive state
166
+ this._isAuthenticated$.next(true);
167
+ this._currentUser$.next(mockResponse.user);
168
+
169
+ return new Observable(subscriber => {
170
+ setTimeout(() => {
171
+ subscriber.next(mockResponse);
172
+ subscriber.complete();
173
+ }, 1000);
174
+ });
175
+ }
176
+
177
+ // Real API call would be:
178
+ return this.http.post<AuthResponse>(\`\${this.API_URL}/auth/login\`, credentials)
179
+ .pipe(
180
+ tap(response => {
181
+ // Store tokens
182
+ this.storage.setItem(environment.auth.tokenKey, response.token);
183
+ this.storage.setItem('current_user', JSON.stringify(response.user));
184
+ if (response.refreshToken) {
185
+ this.storage.setItem(environment.auth.refreshTokenKey, response.refreshToken);
186
+ }
187
+
188
+ // Update reactive state
189
+ this._isAuthenticated$.next(true);
190
+ this._currentUser$.next(response.user);
191
+ }),
192
+ catchError(error => throwError(() => error))
193
+ );
194
+ }
195
+
196
+ logout(): void {
197
+ this.storage.removeItem(environment.auth.tokenKey);
198
+ this.storage.removeItem(environment.auth.refreshTokenKey);
199
+ this.storage.removeItem('current_user');
200
+
201
+ // Update reactive state
202
+ this._isAuthenticated$.next(false);
203
+ this._currentUser$.next(null);
204
+
205
+ this.router.navigate(['/auth/login']);
206
+ }
207
+
208
+ register(userData: RegisterData): Observable<AuthResponse> {
209
+ return this.http.post<AuthResponse>(\`\${this.API_URL}/auth/register\`, userData)
210
+ .pipe(
211
+ tap(response => {
212
+ // Store auth data after successful registration
213
+ this.storage.setItem(environment.auth.tokenKey, response.token);
214
+ this.storage.setItem('current_user', JSON.stringify(response.user));
215
+
216
+ // Update reactive state
217
+ this._isAuthenticated$.next(true);
218
+ this._currentUser$.next(response.user);
219
+ }),
220
+ catchError(error => throwError(() => error))
221
+ );
222
+ }
223
+
224
+ // Synchronous authentication check
225
+ isAuthenticated(): boolean {
226
+ return this.hasValidToken();
227
+ }
228
+
229
+ private hasValidToken(): boolean {
230
+ const token = this.storage.getItem(environment.auth.tokenKey);
231
+ return !!token && !this.isTokenExpired(token);
232
+ }
233
+
234
+ private isTokenExpired(token: string): boolean {
235
+ // For demo token, always return false
236
+ if (token === 'mock_jwt_token_12345') {
237
+ return false;
238
+ }
239
+
240
+ try {
241
+ const payload = JSON.parse(atob(token.split('.')[1]));
242
+ const now = Date.now() / 1000;
243
+ return payload.exp < now;
244
+ } catch {
245
+ return true;
246
+ }
247
+ }
248
+
249
+ private getCurrentUserFromStorage(): User | null {
250
+ try {
251
+ const userStr = this.storage.getItem('current_user');
252
+ return userStr ? JSON.parse(userStr) : null;
253
+ } catch {
254
+ return null;
255
+ }
256
+ }
257
+
258
+ private checkAuthenticationStatus(): void {
259
+ const isAuth = this.hasValidToken();
260
+ const user = this.getCurrentUserFromStorage();
261
+
262
+ this._isAuthenticated$.next(isAuth);
263
+ this._currentUser$.next(user);
264
+ }
265
+
266
+ getToken(): string | null {
267
+ return this.storage.getItem(environment.auth.tokenKey);
268
+ }
269
+
270
+ // Get current user synchronously
271
+ getCurrentUser(): User | null {
272
+ return this._currentUser$.getValue();
273
+ }
274
+ }`;
275
+
276
+ await fs.writeFile(
277
+ path.join(config.fullPath, 'src/app/core/services/auth.service.ts'),
278
+ authService
279
+ );
280
+ }
281
+
282
+ /**
283
+ * Create Storage Service
284
+ */
285
+ async function createStorageService(config) {
286
+ const storageService = `import { Injectable, inject, PLATFORM_ID } from '@angular/core';
287
+ import { isPlatformBrowser } from '@angular/common';
288
+
289
+ @Injectable({
290
+ providedIn: 'root'
291
+ })
292
+ export class StorageService {
293
+ private platformId = inject(PLATFORM_ID);
294
+ private isBrowser = isPlatformBrowser(this.platformId);
295
+
296
+ setItem(key: string, value: string): void {
297
+ if (!this.isBrowser) {
298
+ return;
299
+ }
300
+ try {
301
+ localStorage.setItem(key, value);
302
+ } catch (error) {
303
+ console.error('Error storing to localStorage:', error);
304
+ }
305
+ }
306
+
307
+ getItem(key: string): string | null {
308
+ if (!this.isBrowser) {
309
+ return null;
310
+ }
311
+ try {
312
+ return localStorage.getItem(key);
313
+ } catch (error) {
314
+ console.error('Error retrieving from localStorage:', error);
315
+ return null;
316
+ }
317
+ }
318
+
319
+ removeItem(key: string): void {
320
+ if (!this.isBrowser) {
321
+ return;
322
+ }
323
+ try {
324
+ localStorage.removeItem(key);
325
+ } catch (error) {
326
+ console.error('Error removing from localStorage:', error);
327
+ }
328
+ }
329
+
330
+ clear(): void {
331
+ if (!this.isBrowser) {
332
+ return;
333
+ }
334
+ try {
335
+ localStorage.clear();
336
+ } catch (error) {
337
+ console.error('Error clearing localStorage:', error);
338
+ }
339
+ }
340
+ }`;
341
+
342
+ await fs.writeFile(
343
+ path.join(config.fullPath, 'src/app/core/services/storage.service.ts'),
344
+ storageService
345
+ );
346
+ }
347
+
348
+ /**
349
+ * Create all core services
350
+ */
351
+ async function createCoreServices(config) {
352
+ await createApiService(config);
353
+ await createAuthService(config);
354
+ await createStorageService(config);
355
+ }
356
+
357
+ module.exports = {
358
+ createApiService,
359
+ createAuthService,
360
+ createStorageService,
361
+ createCoreServices,
362
+ };