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
@@ -1,641 +0,0 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
-
4
- /**
5
- * AI Configuration Generator
6
- *
7
- * Strategy:
8
- * Creates AI tool-specific configuration files only when explicitly selected
9
- */
10
-
11
- // ==================== CLAUDE CONFIG ====================
12
-
13
- async function createClaudeConfig(projectPath, projectName) {
14
- const claudeMd = `# ${projectName} - AI Assistant Guidelines
15
-
16
- This is an Angular project scaffolded with **create-ng-tailwind**, using **Angular 20+ and Tailwind CSS v4**.
17
-
18
- ## 📁 Project Structure
19
-
20
- \`\`\`
21
- src/app/
22
- ├── core/ # Core application logic
23
- │ ├── services/ # Auth, API, Toast, Modal, Loading, Cache, Storage, i18n
24
- │ ├── guards/ # Route guards (auth, role-based)
25
- │ ├── interceptors/ # HTTP interceptors (auth, error, loading, caching)
26
- │ └── i18n/ # Translation services
27
- ├── shared/ # Reusable shared code
28
- │ ├── components/ # Button, Card, Spinner, Toast, Modal
29
- │ ├── pipes/ # TimeAgo, Truncate, FileSize, SafeHtml
30
- │ ├── directives/ # ClickOutside, Tooltip
31
- │ └── models/ # TypeScript interfaces and types
32
- ├── features/ # Feature modules
33
- │ ├── home/
34
- │ ├── about/
35
- │ ├── contact/
36
- │ └── auth/ # Login, Register, Forgot Password
37
- └── layout/ # Layout components
38
- ├── header/ # Header with language switcher
39
- ├── footer/
40
- └── auth/ # Auth layout
41
- \`\`\`
42
-
43
- ## 🛠 Technology Stack
44
-
45
- - **Framework:** Angular 20+ (Standalone Components)
46
- - **Styling:** Tailwind CSS v4 (PostCSS)
47
- - **State:** Angular Signals (reactive primitives)
48
- - **HTTP:** HttpClient with 4 Interceptors
49
- - **i18n:** @ngx-translate/core (English & Arabic with RTL)
50
- - **Forms:** Reactive Forms with validation
51
- - **Routing:** Angular Router with lazy loading
52
- - **Icons:** @ng-icons/heroicons
53
- - **Linting:** ESLint + Prettier + simple-git-hooks
54
-
55
- ## 🎯 Path Aliases (Use These!)
56
-
57
- - \`@core/*\` → \`src/app/core/*\`
58
- - \`@shared/*\` → \`src/app/shared/*\`
59
- - \`@features/*\` → \`src/app/features/*\`
60
- - \`@environments/*\` → \`src/environments/*\`
61
-
62
- ## ⚡ HTTP Interceptors (Pre-configured)
63
-
64
- Interceptors run in this order:
65
-
66
- 1. **authInterceptor** → Adds JWT Bearer token to requests
67
- 2. **cachingInterceptor** → Caches GET requests (5min TTL)
68
- 3. **loadingInterceptor** → Shows/hides loading indicator
69
- 4. **errorInterceptor** → Handles errors globally + shows toast
70
-
71
- ## 🧩 Available Services (Use These!)
72
-
73
- ### ToastService
74
-
75
- \`\`\`typescript
76
- import { inject } from '@angular/core';
77
- import { ToastService } from '@core/services/toast.service';
78
-
79
- const toast = inject(ToastService);
80
- toast.success('Profile updated!');
81
- toast.error('Failed to save changes');
82
- toast.warning('Session expiring soon');
83
- toast.info('New message received');
84
- \`\`\`
85
-
86
- ### ModalService
87
-
88
- \`\`\`typescript
89
- import { inject } from '@angular/core';
90
- import { ModalService } from '@core/services/modal.service';
91
-
92
- const modal = inject(ModalService);
93
-
94
- // Confirmation dialog
95
- const confirmed = await modal.confirm(
96
- 'Are you sure you want to delete this item?',
97
- 'Confirm Delete'
98
- );
99
-
100
- if (confirmed) {
101
- // Proceed with deletion
102
- }
103
-
104
- // Alert dialog
105
- await modal.alert('Your session has expired', 'Session Timeout');
106
- \`\`\`
107
-
108
- ### ApiService
109
-
110
- \`\`\`typescript
111
- import { inject } from '@angular/core';
112
- import { ApiService } from '@core/services/api.service';
113
-
114
- const api = inject(ApiService);
115
-
116
- // GET request
117
- api.get<User[]>('users').subscribe((users) => {
118
- console.log(users);
119
- });
120
-
121
- // POST request
122
- api.post<User>('users', userData).subscribe((newUser) => {
123
- console.log('Created:', newUser);
124
- });
125
-
126
- // PUT/DELETE also available
127
- \`\`\`
128
-
129
- ### AuthService
130
-
131
- \`\`\`typescript
132
- import { inject } from '@angular/core';
133
- import { AuthService } from '@core/services/auth.service';
134
-
135
- const auth = inject(AuthService);
136
-
137
- // Login
138
- auth.login({ email, password }).subscribe({
139
- next: () => router.navigate(['/dashboard']),
140
- error: (err) => console.error(err),
141
- });
142
-
143
- // Check auth state (reactive)
144
- auth.isAuthenticated$.subscribe((isAuth) => {
145
- console.log('Authenticated:', isAuth);
146
- });
147
-
148
- // Logout
149
- auth.logout();
150
- \`\`\`
151
-
152
- ### LoadingService
153
-
154
- \`\`\`typescript
155
- import { inject } from '@angular/core';
156
- import { LoadingService } from '@core/services/loading.service';
157
-
158
- const loading = inject(LoadingService);
159
-
160
- // In template (automatic with HTTP interceptor)
161
- @if (loading.isLoading()) {
162
- <app-loading-spinner />
163
- }
164
-
165
- // Manual control (rarely needed)
166
- loading.show();
167
- // ... do work
168
- loading.hide();
169
- \`\`\`
170
-
171
- ## 📝 Coding Patterns
172
-
173
- ### Component Structure (Standalone)
174
-
175
- \`\`\`typescript
176
- import { Component, inject, signal } from '@angular/core';
177
- import { CommonModule } from '@angular/common';
178
- import { ToastService } from '@core/services/toast.service';
179
-
180
- @Component({
181
- selector: 'app-example',
182
- standalone: true,
183
- imports: [CommonModule],
184
- template: \`
185
- <div class="rounded-lg bg-white p-6 shadow">
186
- <h2 class="mb-4 text-xl font-bold">{{ title() }}</h2>
187
- <button
188
- (click)="handleClick()"
189
- class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
190
- >
191
- Click Me ({{ count() }})
192
- </button>
193
- </div>
194
- \`,
195
- })
196
- export class ExampleComponent {
197
- private toast = inject(ToastService);
198
-
199
- // Use signals for reactive state
200
- title = signal('Example Component');
201
- count = signal(0);
202
-
203
- handleClick(): void {
204
- this.count.update((n) => n + 1);
205
- this.toast.success(\`Clicked \${this.count()} times\`);
206
- }
207
- }
208
- \`\`\`
209
-
210
- ### Service Creation
211
-
212
- \`\`\`typescript
213
- import { Injectable, inject, signal } from '@angular/core';
214
- import { ApiService } from '@core/services/api.service';
215
-
216
- interface Item {
217
- id: string;
218
- name: string;
219
- }
220
-
221
- @Injectable({ providedIn: 'root' })
222
- export class ItemService {
223
- private api = inject(ApiService);
224
-
225
- // Use signals for reactive state
226
- items = signal<Item[]>([]);
227
- loading = signal(false);
228
-
229
- loadItems(): void {
230
- this.loading.set(true);
231
- this.api.get<Item[]>('items').subscribe({
232
- next: (data) => {
233
- this.items.set(data);
234
- this.loading.set(false);
235
- },
236
- error: () => this.loading.set(false),
237
- });
238
- }
239
- }
240
- \`\`\`
241
-
242
- ### Reactive Forms
243
-
244
- \`\`\`typescript
245
- import { Component, inject } from '@angular/core';
246
- import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
247
-
248
- @Component({
249
- selector: 'app-contact-form',
250
- standalone: true,
251
- imports: [ReactiveFormsModule],
252
- template: \`
253
- <form [formGroup]="form" (ngSubmit)="onSubmit()">
254
- <input formControlName="email" class="rounded border p-2" />
255
- @if (form.get('email')?.invalid && form.get('email')?.touched) {
256
- <span class="text-sm text-red-500">Email is required</span>
257
- }
258
- <button type="submit" class="rounded bg-blue-500 px-4 py-2 text-white">Submit</button>
259
- </form>
260
- \`,
261
- })
262
- export class ContactFormComponent {
263
- private fb = inject(FormBuilder);
264
-
265
- form = this.fb.group({
266
- email: ['', [Validators.required, Validators.email]],
267
- message: ['', [Validators.required, Validators.minLength(10)]],
268
- });
269
-
270
- onSubmit(): void {
271
- if (this.form.valid) {
272
- console.log(this.form.value);
273
- } else {
274
- Object.keys(this.form.controls).forEach((key) => {
275
- this.form.get(key)?.markAsTouched();
276
- });
277
- }
278
- }
279
- }
280
- \`\`\`
281
-
282
- ### Routing with Lazy Loading
283
-
284
- \`\`\`typescript
285
- import { Routes } from '@angular/router';
286
- import { authGuard } from '@core/guards/auth.guard';
287
-
288
- export const routes: Routes = [
289
- {
290
- path: '',
291
- loadComponent: () => import('./features/home/home.component').then((c) => c.HomeComponent),
292
- },
293
- {
294
- path: 'dashboard',
295
- loadComponent: () =>
296
- import('./features/dashboard/dashboard.component').then((c) => c.DashboardComponent),
297
- canActivate: [authGuard],
298
- },
299
- ];
300
- \`\`\`
301
-
302
- ## 🎨 Styling Guidelines
303
-
304
- **Always use Tailwind CSS classes. Do NOT write custom CSS.**
305
-
306
- Common patterns:
307
-
308
- \`\`\`html
309
- <!-- Layout -->
310
- <div class="container mx-auto px-4">
311
- <div class="flex items-center justify-between">
312
- <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
313
- <!-- Spacing -->
314
- <div class="mt-6 space-y-4 p-4">
315
- <!-- Colors -->
316
- <div class="bg-blue-500 text-white">
317
- <button class="bg-gray-100 text-gray-900 hover:bg-gray-200">
318
- <!-- Typography -->
319
- <h1 class="text-3xl font-bold">
320
- <p class="text-sm text-gray-600">
321
- <!-- Responsive -->
322
- </p>
323
-
324
- <div class="hidden md:block">
325
- <div class="w-full md:w-1/2 lg:w-1/3"></div>
326
- </div>
327
- </h1>
328
- </button>
329
- </div>
330
- </div>
331
- </div>
332
- </div>
333
- </div>
334
- \`\`\`
335
-
336
- ## 🌍 Internationalization (i18n)
337
-
338
- Support English and Arabic with RTL:
339
-
340
- **Add translations to both files:**
341
-
342
- - \`public/assets/i18n/en.json\`
343
- - \`public/assets/i18n/ar.json\`
344
-
345
- **In templates:**
346
-
347
- \`\`\`html
348
- <h1>{{ 'welcome.title' | translate }}</h1>
349
- <button>{{ 'common.save' | translate }}</button>
350
- \`\`\`
351
-
352
- **In components:**
353
-
354
- \`\`\`typescript
355
- import { inject } from '@angular/core';
356
- import { TranslateService } from '@ngx-translate/core';
357
-
358
- const translate = inject(TranslateService);
359
- const message = translate.instant('error.notFound');
360
- \`\`\`
361
-
362
- ## ✅ Best Practices
363
-
364
- 1. **Always use standalone components** (no NgModule)
365
- 2. **Use inject() function** (not constructor injection)
366
- 3. **Use signals for reactive state** (not BehaviorSubject unless needed)
367
- 4. **Use existing services** before creating new ones
368
- 5. **Use Tailwind CSS classes** (no custom CSS)
369
- 6. **Use path aliases** (@core/, @shared/, @features/)
370
- 7. **Use lazy loading** for all routes
371
- 8. **Use Reactive Forms** (not template-driven)
372
- 9. **Write unit tests** for services and components
373
- 10. **Support RTL** for Arabic language
374
-
375
- ## ❌ Don't Do This
376
-
377
- - ❌ Don't use NgModule (use standalone)
378
- - ❌ Don't use constructor injection (use inject())
379
- - ❌ Don't write custom CSS (use Tailwind)
380
- - ❌ Don't use \`any\` type (use proper TypeScript types)
381
- - ❌ Don't use \`src/assets/\` (use \`public/assets/\`)
382
- - ❌ Don't use HttpClient directly (use ApiService)
383
- - ❌ Don't create services that already exist
384
- - ❌ Don't use template-driven forms
385
- - ❌ Don't forget to add translations for new text
386
-
387
- ## 📦 Available Pipes
388
-
389
- - \`truncate:50:true\` - Truncate text to 50 chars with word boundaries
390
- - \`translate\` - i18n translations (from @ngx-translate)
391
-
392
- ## 📦 Available Directives
393
-
394
- - \`appClickOutside\` - Detect clicks outside element
395
- - \`appTooltip="text"\` - Show tooltip on hover
396
-
397
- ## 🧪 Testing
398
-
399
- Write unit tests using Jasmine/Karma:
400
-
401
- \`\`\`typescript
402
- describe('ExampleComponent', () => {
403
- let component: ExampleComponent;
404
- let fixture: ComponentFixture<ExampleComponent>;
405
-
406
- beforeEach(() => {
407
- TestBed.configureTestingModule({
408
- imports: [ExampleComponent],
409
- });
410
- fixture = TestBed.createComponent(ExampleComponent);
411
- component = fixture.componentInstance;
412
- });
413
-
414
- it('should create', () => {
415
- expect(component).toBeTruthy();
416
- });
417
- });
418
- \`\`\`
419
-
420
- ## 📚 Important Notes
421
-
422
- - Assets go in \`public/assets/\` (not \`src/assets/\`)
423
- - Tailwind v4 uses CSS imports: \`@import "tailwindcss";\`
424
- - PostCSS config is in \`.postcssrc.json\`
425
- - HTTP interceptors run automatically (no manual setup needed)
426
- - Loading state is automatic for HTTP requests
427
- - Error handling is automatic (shows toast notifications)
428
-
429
- ---
430
-
431
- **Remember:** This is a production-ready starter. Use the existing infrastructure before creating new utilities!
432
- `;
433
-
434
- // Create .claude directory if it doesn't exist
435
- const claudeDir = path.join(projectPath, '.claude');
436
- await fs.ensureDir(claudeDir);
437
-
438
- // Write CLAUDE.md inside .claude folder (uppercase to match Angular CLI convention)
439
- await fs.writeFile(path.join(claudeDir, 'CLAUDE.md'), claudeMd);
440
- }
441
-
442
- // ==================== CURSOR-SPECIFIC CONFIG ====================
443
-
444
- async function createCursorConfig(projectPath, projectName) {
445
- const cursorRules = `# ${projectName} - Cursor AI Rules
446
-
447
- ## Quick Reference
448
-
449
- - Angular 20+ standalone components
450
- - Tailwind CSS v4 (use utility classes only)
451
- - inject() for DI (not constructors)
452
- - Signals for state (not BehaviorSubject)
453
- - Path aliases: @core/, @shared/, @features/, @environments/
454
-
455
- ## Component Pattern
456
-
457
- \`\`\`typescript
458
- import { Component, inject, signal } from '@angular/core';
459
- import { CommonModule } from '@angular/common';
460
-
461
- @Component({
462
- selector: 'app-name',
463
- standalone: true,
464
- imports: [CommonModule],
465
- template: \`<div class="p-4">{{ text() }}</div>\`,
466
- })
467
- export class NameComponent {
468
- private service = inject(ServiceName);
469
- text = signal('Hello');
470
- }
471
- \`\`\`
472
-
473
- ## Available Services (Use These!)
474
-
475
- - **ToastService** - \`toast.success('Message')\`
476
- - **ModalService** - \`await modal.confirm('Message')\`
477
- - **ApiService** - \`api.get<Type>('endpoint')\`
478
- - **AuthService** - \`auth.login(credentials)\`
479
- - **LoadingService** - \`loading.isLoading()\`
480
-
481
- ## HTTP Interceptors (Automatic)
482
-
483
- Auth → Cache → Loading → Error
484
-
485
- ## Forms: Use Reactive Forms
486
-
487
- \`\`\`typescript
488
- form = inject(FormBuilder).group({
489
- email: ['', [Validators.required, Validators.email]],
490
- });
491
- \`\`\`
492
-
493
- ## Routing: Use Lazy Loading
494
-
495
- \`\`\`typescript
496
- {
497
- path: 'feature',
498
- loadComponent: () =>
499
- import('./feature/feature.component').then((c) => c.FeatureComponent),
500
- }
501
- \`\`\`
502
-
503
- ## Styling: Tailwind Only
504
-
505
- Use: \`rounded bg-blue-500 p-4 text-white hover:bg-blue-600\`
506
-
507
- ## i18n: Support English + Arabic
508
-
509
- Add keys to \`public/assets/i18n/en.json\` and \`ar.json\`
510
-
511
- ## Don't
512
-
513
- - No NgModule
514
- - No constructor injection
515
- - No custom CSS
516
- - No \`any\` type
517
- - No \`src/assets/\` (use \`public/assets/\`)
518
- - No HttpClient directly (use ApiService)
519
- `;
520
-
521
- await fs.writeFile(path.join(projectPath, '.cursorrules'), cursorRules);
522
- }
523
-
524
- // ==================== WINDSURF-SPECIFIC CONFIG ====================
525
-
526
- async function createWindsurfConfig(projectPath, projectName) {
527
- const windsurfRules = `# ${projectName} - Windsurf AI Rules
528
-
529
- Angular 20+ | Tailwind CSS v4 | Standalone Components | Signals
530
-
531
- ## Quick Patterns
532
-
533
- **Component:**
534
-
535
- \`\`\`typescript
536
- @Component({
537
- selector: 'app-name',
538
- standalone: true,
539
- imports: [CommonModule],
540
- template: \`<div class="p-4">{{ state() }}</div>\`,
541
- })
542
- export class NameComponent {
543
- private svc = inject(Service);
544
- state = signal(value);
545
- }
546
- \`\`\`
547
-
548
- **Show Toast:**
549
-
550
- \`\`\`typescript
551
- inject(ToastService).success('Message');
552
- \`\`\`
553
-
554
- **Open Modal:**
555
-
556
- \`\`\`typescript
557
- await inject(ModalService).confirm('Message');
558
- \`\`\`
559
-
560
- **API Call:**
561
-
562
- \`\`\`typescript
563
- inject(ApiService).get<Type>('endpoint').subscribe(...);
564
- \`\`\`
565
-
566
- **Form:**
567
-
568
- \`\`\`typescript
569
- form = inject(FormBuilder).group({
570
- field: ['', Validators.required],
571
- });
572
- \`\`\`
573
-
574
- ## Path Aliases
575
-
576
- @core/ @shared/ @features/ @environments/
577
-
578
- ## Services Available
579
-
580
- ToastService, ModalService, ApiService, AuthService, LoadingService, CacheService
581
-
582
- ## HTTP Interceptors (Auto)
583
-
584
- Auth → Cache → Loading → Error
585
-
586
- ## Styling
587
-
588
- Tailwind only: rounded bg-blue-500 p-4 text-white
589
-
590
- ## i18n
591
-
592
- public/assets/i18n/en.json + ar.json
593
-
594
- ## Don't
595
-
596
- No NgModule, No constructors, No custom CSS, No 'any', No src/assets
597
- `;
598
-
599
- await fs.writeFile(path.join(projectPath, '.windsurfrules'), windsurfRules);
600
- }
601
-
602
- // ==================== MAIN EXPORT ====================
603
-
604
- /**
605
- * Create AI configuration files
606
- * Creates tool-specific configs only when explicitly selected
607
- *
608
- * @param {string} projectPath - Full path to the project
609
- * @param {string} projectName - Name of the project
610
- * @param {string[]} aiTools - Array of selected AI tools
611
- */
612
- async function createAIConfigs(projectPath, projectName, aiTools) {
613
- // Skip if user selected "none"
614
- if (!aiTools || aiTools.length === 0 || (aiTools.length === 1 && aiTools[0] === 'none')) {
615
- return;
616
- }
617
-
618
- console.log('\n🤖 Configuring AI tools...');
619
-
620
- // Create tool-specific configs only if selected
621
- if (aiTools.includes('claude')) {
622
- await createClaudeConfig(projectPath, projectName);
623
- console.log(' ✓ .claude/CLAUDE.md created');
624
- }
625
-
626
- if (aiTools.includes('cursor')) {
627
- await createCursorConfig(projectPath, projectName);
628
- console.log(' ✓ .cursorrules created');
629
- }
630
-
631
- if (aiTools.includes('windsurf')) {
632
- await createWindsurfConfig(projectPath, projectName);
633
- console.log(' ✓ .windsurfrules created');
634
- }
635
-
636
- console.log('✨ AI configuration complete!\n');
637
- }
638
-
639
- module.exports = {
640
- createAIConfigs,
641
- };
File without changes
File without changes
File without changes