create-ng-tailwind 2.0.1 → 2.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/CHANGELOG.md +85 -0
- package/README.md +81 -177
- package/lib/managers/ProjectManager.js +2 -2
- package/lib/templates/starter/features.js +49 -7
- package/lib/templates/starter/index.js +121 -48
- package/lib/templates/starter/seo-assets.js +141 -0
- package/lib/templates/starter/seo-features.js +290 -0
- package/lib/utils/ai-config.js +97 -58
- package/package.json +3 -4
- package/CLAUDE.md +0 -178
package/lib/utils/ai-config.js
CHANGED
|
@@ -62,6 +62,7 @@ src/app/
|
|
|
62
62
|
## ⚡ HTTP Interceptors (Pre-configured)
|
|
63
63
|
|
|
64
64
|
Interceptors run in this order:
|
|
65
|
+
|
|
65
66
|
1. **authInterceptor** → Adds JWT Bearer token to requests
|
|
66
67
|
2. **cachingInterceptor** → Caches GET requests (5min TTL)
|
|
67
68
|
3. **loadingInterceptor** → Shows/hides loading indicator
|
|
@@ -70,6 +71,7 @@ Interceptors run in this order:
|
|
|
70
71
|
## 🧩 Available Services (Use These!)
|
|
71
72
|
|
|
72
73
|
### ToastService
|
|
74
|
+
|
|
73
75
|
\`\`\`typescript
|
|
74
76
|
import { inject } from '@angular/core';
|
|
75
77
|
import { ToastService } from '@core/services/toast.service';
|
|
@@ -82,6 +84,7 @@ toast.info('New message received');
|
|
|
82
84
|
\`\`\`
|
|
83
85
|
|
|
84
86
|
### ModalService
|
|
87
|
+
|
|
85
88
|
\`\`\`typescript
|
|
86
89
|
import { inject } from '@angular/core';
|
|
87
90
|
import { ModalService } from '@core/services/modal.service';
|
|
@@ -103,6 +106,7 @@ await modal.alert('Your session has expired', 'Session Timeout');
|
|
|
103
106
|
\`\`\`
|
|
104
107
|
|
|
105
108
|
### ApiService
|
|
109
|
+
|
|
106
110
|
\`\`\`typescript
|
|
107
111
|
import { inject } from '@angular/core';
|
|
108
112
|
import { ApiService } from '@core/services/api.service';
|
|
@@ -110,12 +114,12 @@ import { ApiService } from '@core/services/api.service';
|
|
|
110
114
|
const api = inject(ApiService);
|
|
111
115
|
|
|
112
116
|
// GET request
|
|
113
|
-
api.get<User[]>('users').subscribe(users => {
|
|
117
|
+
api.get<User[]>('users').subscribe((users) => {
|
|
114
118
|
console.log(users);
|
|
115
119
|
});
|
|
116
120
|
|
|
117
121
|
// POST request
|
|
118
|
-
api.post<User>('users', userData).subscribe(newUser => {
|
|
122
|
+
api.post<User>('users', userData).subscribe((newUser) => {
|
|
119
123
|
console.log('Created:', newUser);
|
|
120
124
|
});
|
|
121
125
|
|
|
@@ -123,6 +127,7 @@ api.post<User>('users', userData).subscribe(newUser => {
|
|
|
123
127
|
\`\`\`
|
|
124
128
|
|
|
125
129
|
### AuthService
|
|
130
|
+
|
|
126
131
|
\`\`\`typescript
|
|
127
132
|
import { inject } from '@angular/core';
|
|
128
133
|
import { AuthService } from '@core/services/auth.service';
|
|
@@ -132,11 +137,11 @@ const auth = inject(AuthService);
|
|
|
132
137
|
// Login
|
|
133
138
|
auth.login({ email, password }).subscribe({
|
|
134
139
|
next: () => router.navigate(['/dashboard']),
|
|
135
|
-
error: (err) => console.error(err)
|
|
140
|
+
error: (err) => console.error(err),
|
|
136
141
|
});
|
|
137
142
|
|
|
138
143
|
// Check auth state (reactive)
|
|
139
|
-
auth.isAuthenticated$.subscribe(isAuth => {
|
|
144
|
+
auth.isAuthenticated$.subscribe((isAuth) => {
|
|
140
145
|
console.log('Authenticated:', isAuth);
|
|
141
146
|
});
|
|
142
147
|
|
|
@@ -145,6 +150,7 @@ auth.logout();
|
|
|
145
150
|
\`\`\`
|
|
146
151
|
|
|
147
152
|
### LoadingService
|
|
153
|
+
|
|
148
154
|
\`\`\`typescript
|
|
149
155
|
import { inject } from '@angular/core';
|
|
150
156
|
import { LoadingService } from '@core/services/loading.service';
|
|
@@ -165,6 +171,7 @@ loading.hide();
|
|
|
165
171
|
## 📝 Coding Patterns
|
|
166
172
|
|
|
167
173
|
### Component Structure (Standalone)
|
|
174
|
+
|
|
168
175
|
\`\`\`typescript
|
|
169
176
|
import { Component, inject, signal } from '@angular/core';
|
|
170
177
|
import { CommonModule } from '@angular/common';
|
|
@@ -175,15 +182,16 @@ import { ToastService } from '@core/services/toast.service';
|
|
|
175
182
|
standalone: true,
|
|
176
183
|
imports: [CommonModule],
|
|
177
184
|
template: \`
|
|
178
|
-
<div class="
|
|
179
|
-
<h2 class="text-xl font-bold
|
|
185
|
+
<div class="rounded-lg bg-white p-6 shadow">
|
|
186
|
+
<h2 class="mb-4 text-xl font-bold">{{ title() }}</h2>
|
|
180
187
|
<button
|
|
181
188
|
(click)="handleClick()"
|
|
182
|
-
class="px-4 py-2
|
|
189
|
+
class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
|
|
190
|
+
>
|
|
183
191
|
Click Me ({{ count() }})
|
|
184
192
|
</button>
|
|
185
193
|
</div>
|
|
186
|
-
|
|
194
|
+
\`,
|
|
187
195
|
})
|
|
188
196
|
export class ExampleComponent {
|
|
189
197
|
private toast = inject(ToastService);
|
|
@@ -193,13 +201,14 @@ export class ExampleComponent {
|
|
|
193
201
|
count = signal(0);
|
|
194
202
|
|
|
195
203
|
handleClick(): void {
|
|
196
|
-
this.count.update(n => n + 1);
|
|
204
|
+
this.count.update((n) => n + 1);
|
|
197
205
|
this.toast.success(\`Clicked \${this.count()} times\`);
|
|
198
206
|
}
|
|
199
207
|
}
|
|
200
208
|
\`\`\`
|
|
201
209
|
|
|
202
210
|
### Service Creation
|
|
211
|
+
|
|
203
212
|
\`\`\`typescript
|
|
204
213
|
import { Injectable, inject, signal } from '@angular/core';
|
|
205
214
|
import { ApiService } from '@core/services/api.service';
|
|
@@ -224,13 +233,14 @@ export class ItemService {
|
|
|
224
233
|
this.items.set(data);
|
|
225
234
|
this.loading.set(false);
|
|
226
235
|
},
|
|
227
|
-
error: () => this.loading.set(false)
|
|
236
|
+
error: () => this.loading.set(false),
|
|
228
237
|
});
|
|
229
238
|
}
|
|
230
239
|
}
|
|
231
240
|
\`\`\`
|
|
232
241
|
|
|
233
242
|
### Reactive Forms
|
|
243
|
+
|
|
234
244
|
\`\`\`typescript
|
|
235
245
|
import { Component, inject } from '@angular/core';
|
|
236
246
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
@@ -241,29 +251,27 @@ import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
|
241
251
|
imports: [ReactiveFormsModule],
|
|
242
252
|
template: \`
|
|
243
253
|
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
244
|
-
<input formControlName="email" class="border p-2
|
|
254
|
+
<input formControlName="email" class="rounded border p-2" />
|
|
245
255
|
@if (form.get('email')?.invalid && form.get('email')?.touched) {
|
|
246
|
-
<span class="text-red-500
|
|
256
|
+
<span class="text-sm text-red-500">Email is required</span>
|
|
247
257
|
}
|
|
248
|
-
<button type="submit" class="px-4 py-2
|
|
249
|
-
Submit
|
|
250
|
-
</button>
|
|
258
|
+
<button type="submit" class="rounded bg-blue-500 px-4 py-2 text-white">Submit</button>
|
|
251
259
|
</form>
|
|
252
|
-
|
|
260
|
+
\`,
|
|
253
261
|
})
|
|
254
262
|
export class ContactFormComponent {
|
|
255
263
|
private fb = inject(FormBuilder);
|
|
256
264
|
|
|
257
265
|
form = this.fb.group({
|
|
258
266
|
email: ['', [Validators.required, Validators.email]],
|
|
259
|
-
message: ['', [Validators.required, Validators.minLength(10)]]
|
|
267
|
+
message: ['', [Validators.required, Validators.minLength(10)]],
|
|
260
268
|
});
|
|
261
269
|
|
|
262
270
|
onSubmit(): void {
|
|
263
271
|
if (this.form.valid) {
|
|
264
272
|
console.log(this.form.value);
|
|
265
273
|
} else {
|
|
266
|
-
Object.keys(this.form.controls).forEach(key => {
|
|
274
|
+
Object.keys(this.form.controls).forEach((key) => {
|
|
267
275
|
this.form.get(key)?.markAsTouched();
|
|
268
276
|
});
|
|
269
277
|
}
|
|
@@ -272,6 +280,7 @@ export class ContactFormComponent {
|
|
|
272
280
|
\`\`\`
|
|
273
281
|
|
|
274
282
|
### Routing with Lazy Loading
|
|
283
|
+
|
|
275
284
|
\`\`\`typescript
|
|
276
285
|
import { Routes } from '@angular/router';
|
|
277
286
|
import { authGuard } from '@core/guards/auth.guard';
|
|
@@ -279,15 +288,14 @@ import { authGuard } from '@core/guards/auth.guard';
|
|
|
279
288
|
export const routes: Routes = [
|
|
280
289
|
{
|
|
281
290
|
path: '',
|
|
282
|
-
loadComponent: () => import('./features/home/home.component')
|
|
283
|
-
.then(c => c.HomeComponent)
|
|
291
|
+
loadComponent: () => import('./features/home/home.component').then((c) => c.HomeComponent),
|
|
284
292
|
},
|
|
285
293
|
{
|
|
286
294
|
path: 'dashboard',
|
|
287
|
-
loadComponent: () =>
|
|
288
|
-
.then(c => c.DashboardComponent),
|
|
289
|
-
canActivate: [authGuard]
|
|
290
|
-
}
|
|
295
|
+
loadComponent: () =>
|
|
296
|
+
import('./features/dashboard/dashboard.component').then((c) => c.DashboardComponent),
|
|
297
|
+
canActivate: [authGuard],
|
|
298
|
+
},
|
|
291
299
|
];
|
|
292
300
|
\`\`\`
|
|
293
301
|
|
|
@@ -296,26 +304,33 @@ export const routes: Routes = [
|
|
|
296
304
|
**Always use Tailwind CSS classes. Do NOT write custom CSS.**
|
|
297
305
|
|
|
298
306
|
Common patterns:
|
|
307
|
+
|
|
299
308
|
\`\`\`html
|
|
300
309
|
<!-- Layout -->
|
|
301
310
|
<div class="container mx-auto px-4">
|
|
302
|
-
<div class="flex items-center justify-between">
|
|
303
|
-
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
<
|
|
311
|
-
|
|
312
|
-
<!--
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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>
|
|
319
334
|
\`\`\`
|
|
320
335
|
|
|
321
336
|
## 🌍 Internationalization (i18n)
|
|
@@ -323,16 +338,19 @@ Common patterns:
|
|
|
323
338
|
Support English and Arabic with RTL:
|
|
324
339
|
|
|
325
340
|
**Add translations to both files:**
|
|
341
|
+
|
|
326
342
|
- \`public/assets/i18n/en.json\`
|
|
327
343
|
- \`public/assets/i18n/ar.json\`
|
|
328
344
|
|
|
329
345
|
**In templates:**
|
|
346
|
+
|
|
330
347
|
\`\`\`html
|
|
331
348
|
<h1>{{ 'welcome.title' | translate }}</h1>
|
|
332
349
|
<button>{{ 'common.save' | translate }}</button>
|
|
333
350
|
\`\`\`
|
|
334
351
|
|
|
335
352
|
**In components:**
|
|
353
|
+
|
|
336
354
|
\`\`\`typescript
|
|
337
355
|
import { inject } from '@angular/core';
|
|
338
356
|
import { TranslateService } from '@ngx-translate/core';
|
|
@@ -379,6 +397,7 @@ const message = translate.instant('error.notFound');
|
|
|
379
397
|
## 🧪 Testing
|
|
380
398
|
|
|
381
399
|
Write unit tests using Jasmine/Karma:
|
|
400
|
+
|
|
382
401
|
\`\`\`typescript
|
|
383
402
|
describe('ExampleComponent', () => {
|
|
384
403
|
let component: ExampleComponent;
|
|
@@ -386,7 +405,7 @@ describe('ExampleComponent', () => {
|
|
|
386
405
|
|
|
387
406
|
beforeEach(() => {
|
|
388
407
|
TestBed.configureTestingModule({
|
|
389
|
-
imports: [ExampleComponent]
|
|
408
|
+
imports: [ExampleComponent],
|
|
390
409
|
});
|
|
391
410
|
fixture = TestBed.createComponent(ExampleComponent);
|
|
392
411
|
component = fixture.componentInstance;
|
|
@@ -416,8 +435,8 @@ describe('ExampleComponent', () => {
|
|
|
416
435
|
const claudeDir = path.join(projectPath, ".claude");
|
|
417
436
|
await fs.ensureDir(claudeDir);
|
|
418
437
|
|
|
419
|
-
// Write
|
|
420
|
-
await fs.writeFile(path.join(claudeDir, "
|
|
438
|
+
// Write CLAUDE.md inside .claude folder (uppercase to match Angular CLI convention)
|
|
439
|
+
await fs.writeFile(path.join(claudeDir, "CLAUDE.md"), claudeMd);
|
|
421
440
|
}
|
|
422
441
|
|
|
423
442
|
// ==================== CURSOR-SPECIFIC CONFIG ====================
|
|
@@ -426,6 +445,7 @@ async function createCursorConfig(projectPath, projectName) {
|
|
|
426
445
|
const cursorRules = `# ${projectName} - Cursor AI Rules
|
|
427
446
|
|
|
428
447
|
## Quick Reference
|
|
448
|
+
|
|
429
449
|
- Angular 20+ standalone components
|
|
430
450
|
- Tailwind CSS v4 (use utility classes only)
|
|
431
451
|
- inject() for DI (not constructors)
|
|
@@ -433,6 +453,7 @@ async function createCursorConfig(projectPath, projectName) {
|
|
|
433
453
|
- Path aliases: @core/, @shared/, @features/, @environments/
|
|
434
454
|
|
|
435
455
|
## Component Pattern
|
|
456
|
+
|
|
436
457
|
\`\`\`typescript
|
|
437
458
|
import { Component, inject, signal } from '@angular/core';
|
|
438
459
|
import { CommonModule } from '@angular/common';
|
|
@@ -441,7 +462,7 @@ import { CommonModule } from '@angular/common';
|
|
|
441
462
|
selector: 'app-name',
|
|
442
463
|
standalone: true,
|
|
443
464
|
imports: [CommonModule],
|
|
444
|
-
template: \`<div class="p-4">{{ text() }}</div
|
|
465
|
+
template: \`<div class="p-4">{{ text() }}</div>\`,
|
|
445
466
|
})
|
|
446
467
|
export class NameComponent {
|
|
447
468
|
private service = inject(ServiceName);
|
|
@@ -450,6 +471,7 @@ export class NameComponent {
|
|
|
450
471
|
\`\`\`
|
|
451
472
|
|
|
452
473
|
## Available Services (Use These!)
|
|
474
|
+
|
|
453
475
|
- **ToastService** - \`toast.success('Message')\`
|
|
454
476
|
- **ModalService** - \`await modal.confirm('Message')\`
|
|
455
477
|
- **ApiService** - \`api.get<Type>('endpoint')\`
|
|
@@ -457,31 +479,37 @@ export class NameComponent {
|
|
|
457
479
|
- **LoadingService** - \`loading.isLoading()\`
|
|
458
480
|
|
|
459
481
|
## HTTP Interceptors (Automatic)
|
|
482
|
+
|
|
460
483
|
Auth → Cache → Loading → Error
|
|
461
484
|
|
|
462
485
|
## Forms: Use Reactive Forms
|
|
486
|
+
|
|
463
487
|
\`\`\`typescript
|
|
464
488
|
form = inject(FormBuilder).group({
|
|
465
|
-
email: ['', [Validators.required, Validators.email]]
|
|
489
|
+
email: ['', [Validators.required, Validators.email]],
|
|
466
490
|
});
|
|
467
491
|
\`\`\`
|
|
468
492
|
|
|
469
493
|
## Routing: Use Lazy Loading
|
|
494
|
+
|
|
470
495
|
\`\`\`typescript
|
|
471
496
|
{
|
|
472
497
|
path: 'feature',
|
|
473
|
-
loadComponent: () =>
|
|
474
|
-
.then(c => c.FeatureComponent)
|
|
498
|
+
loadComponent: () =>
|
|
499
|
+
import('./feature/feature.component').then((c) => c.FeatureComponent),
|
|
475
500
|
}
|
|
476
501
|
\`\`\`
|
|
477
502
|
|
|
478
503
|
## Styling: Tailwind Only
|
|
479
|
-
|
|
504
|
+
|
|
505
|
+
Use: \`rounded bg-blue-500 p-4 text-white hover:bg-blue-600\`
|
|
480
506
|
|
|
481
507
|
## i18n: Support English + Arabic
|
|
508
|
+
|
|
482
509
|
Add keys to \`public/assets/i18n/en.json\` and \`ar.json\`
|
|
483
510
|
|
|
484
511
|
## Don't
|
|
512
|
+
|
|
485
513
|
- No NgModule
|
|
486
514
|
- No constructor injection
|
|
487
515
|
- No custom CSS
|
|
@@ -502,13 +530,14 @@ Angular 20+ | Tailwind CSS v4 | Standalone Components | Signals
|
|
|
502
530
|
|
|
503
531
|
## Quick Patterns
|
|
504
532
|
|
|
505
|
-
Component
|
|
533
|
+
**Component:**
|
|
534
|
+
|
|
506
535
|
\`\`\`typescript
|
|
507
536
|
@Component({
|
|
508
537
|
selector: 'app-name',
|
|
509
538
|
standalone: true,
|
|
510
539
|
imports: [CommonModule],
|
|
511
|
-
template: \`<div class="p-4">{{ state() }}</div
|
|
540
|
+
template: \`<div class="p-4">{{ state() }}</div>\`,
|
|
512
541
|
})
|
|
513
542
|
export class NameComponent {
|
|
514
543
|
private svc = inject(Service);
|
|
@@ -516,44 +545,54 @@ export class NameComponent {
|
|
|
516
545
|
}
|
|
517
546
|
\`\`\`
|
|
518
547
|
|
|
519
|
-
Show Toast
|
|
548
|
+
**Show Toast:**
|
|
549
|
+
|
|
520
550
|
\`\`\`typescript
|
|
521
551
|
inject(ToastService).success('Message');
|
|
522
552
|
\`\`\`
|
|
523
553
|
|
|
524
|
-
Open Modal
|
|
554
|
+
**Open Modal:**
|
|
555
|
+
|
|
525
556
|
\`\`\`typescript
|
|
526
557
|
await inject(ModalService).confirm('Message');
|
|
527
558
|
\`\`\`
|
|
528
559
|
|
|
529
|
-
API Call
|
|
560
|
+
**API Call:**
|
|
561
|
+
|
|
530
562
|
\`\`\`typescript
|
|
531
563
|
inject(ApiService).get<Type>('endpoint').subscribe(...);
|
|
532
564
|
\`\`\`
|
|
533
565
|
|
|
534
|
-
Form
|
|
566
|
+
**Form:**
|
|
567
|
+
|
|
535
568
|
\`\`\`typescript
|
|
536
569
|
form = inject(FormBuilder).group({
|
|
537
|
-
field: ['', Validators.required]
|
|
570
|
+
field: ['', Validators.required],
|
|
538
571
|
});
|
|
539
572
|
\`\`\`
|
|
540
573
|
|
|
541
574
|
## Path Aliases
|
|
575
|
+
|
|
542
576
|
@core/ @shared/ @features/ @environments/
|
|
543
577
|
|
|
544
578
|
## Services Available
|
|
579
|
+
|
|
545
580
|
ToastService, ModalService, ApiService, AuthService, LoadingService, CacheService
|
|
546
581
|
|
|
547
582
|
## HTTP Interceptors (Auto)
|
|
583
|
+
|
|
548
584
|
Auth → Cache → Loading → Error
|
|
549
585
|
|
|
550
586
|
## Styling
|
|
551
|
-
|
|
587
|
+
|
|
588
|
+
Tailwind only: rounded bg-blue-500 p-4 text-white
|
|
552
589
|
|
|
553
590
|
## i18n
|
|
591
|
+
|
|
554
592
|
public/assets/i18n/en.json + ar.json
|
|
555
593
|
|
|
556
594
|
## Don't
|
|
595
|
+
|
|
557
596
|
No NgModule, No constructors, No custom CSS, No 'any', No src/assets
|
|
558
597
|
`;
|
|
559
598
|
|
|
@@ -585,7 +624,7 @@ async function createAIConfigs(projectPath, projectName, aiTools) {
|
|
|
585
624
|
// Create tool-specific configs only if selected
|
|
586
625
|
if (aiTools.includes("claude")) {
|
|
587
626
|
await createClaudeConfig(projectPath, projectName);
|
|
588
|
-
console.log(" ✓ .claude/
|
|
627
|
+
console.log(" ✓ .claude/CLAUDE.md created");
|
|
589
628
|
}
|
|
590
629
|
|
|
591
630
|
if (aiTools.includes("cursor")) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-ng-tailwind",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "🚀
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "🚀 A CLI tool to give starter template for angular project with tailwind css",
|
|
5
5
|
"main": "bin/create-ng-tailwind.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-ng-tailwind": "./bin/create-ng-tailwind.js"
|
|
@@ -53,8 +53,7 @@
|
|
|
53
53
|
"lib/",
|
|
54
54
|
"README.md",
|
|
55
55
|
"LICENSE",
|
|
56
|
-
"CHANGELOG.md"
|
|
57
|
-
"CLAUDE.md"
|
|
56
|
+
"CHANGELOG.md"
|
|
58
57
|
],
|
|
59
58
|
"dependencies": {
|
|
60
59
|
"chalk": "^4.1.2",
|
package/CLAUDE.md
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
`create-ng-tailwind` is a CLI tool that scaffolds Angular projects with Tailwind CSS preconfigured. It wraps the Angular CLI (`ng new`) and adds Tailwind v4 setup, templates, and best practices. The tool operates in two modes:
|
|
8
|
-
|
|
9
|
-
1. **Interactive Mode (Default)**: Prompts users for configuration like `ng new` does
|
|
10
|
-
2. **Fast Mode**: Accepts all options via CLI flags for CI/CD workflows
|
|
11
|
-
|
|
12
|
-
## Architecture
|
|
13
|
-
|
|
14
|
-
The codebase follows a modular architecture with clear separation of concerns:
|
|
15
|
-
|
|
16
|
-
### Core Components
|
|
17
|
-
|
|
18
|
-
1. **CLI Layer** (`lib/cli/`)
|
|
19
|
-
- `index.js`: Main CLI handler - parses arguments, orchestrates interactive prompts, delegates to ProjectManager
|
|
20
|
-
- `interactive.js`: Interactive prompt logic
|
|
21
|
-
- `validators.js`: Input validation utilities
|
|
22
|
-
|
|
23
|
-
2. **Manager Layer** (`lib/managers/`)
|
|
24
|
-
- `ProjectManager.js`: Orchestrates project creation - wraps `ng new`, coordinates TailwindManager and TemplateManager
|
|
25
|
-
- `TailwindManager.js`: Handles Tailwind v4 installation and PostCSS configuration
|
|
26
|
-
- `TemplateManager.js`: Applies selected template to the project
|
|
27
|
-
|
|
28
|
-
3. **Template System** (`lib/templates/`)
|
|
29
|
-
- `minimal/`: Minimal template (just Angular + Tailwind)
|
|
30
|
-
- `starter/`: Professional foundation with components, routing, i18n, auth UI, services, interceptors, etc.
|
|
31
|
-
- Templates export an `apply(config)` async function that modifies the generated Angular project
|
|
32
|
-
|
|
33
|
-
4. **Utilities** (`lib/utils/`)
|
|
34
|
-
- `logger.js`: Styled CLI output with ora spinners
|
|
35
|
-
- `helpers.js`: Shared utility functions
|
|
36
|
-
- `constants.js`: Configuration constants
|
|
37
|
-
|
|
38
|
-
### Key Workflow
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
bin/create-ng-tailwind.js → lib/cli/index.js → lib/managers/ProjectManager.js
|
|
42
|
-
├─> lib/managers/TailwindManager.js
|
|
43
|
-
└─> lib/managers/TemplateManager.js
|
|
44
|
-
└─> lib/templates/{minimal|starter}/index.js
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Development Commands
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
# Test locally during development
|
|
51
|
-
npm run dev my-test-app
|
|
52
|
-
|
|
53
|
-
# Test with CLI flags (skip prompts for fast testing)
|
|
54
|
-
npm run dev my-app -- --template=starter
|
|
55
|
-
|
|
56
|
-
# Test interactive mode (will prompt for all options)
|
|
57
|
-
npm run dev my-app
|
|
58
|
-
|
|
59
|
-
# Quick demo
|
|
60
|
-
npm run demo
|
|
61
|
-
|
|
62
|
-
# Link for global testing
|
|
63
|
-
npm link
|
|
64
|
-
create-ng-tailwind test-app
|
|
65
|
-
|
|
66
|
-
# Unlink when done testing
|
|
67
|
-
npm unlink -g create-ng-tailwind
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Key Technical Details
|
|
71
|
-
|
|
72
|
-
### Tailwind v4 Setup
|
|
73
|
-
|
|
74
|
-
The tool uses Tailwind v4's modern PostCSS plugin approach:
|
|
75
|
-
- `.postcssrc.json` with `@tailwindcss/postcss` plugin
|
|
76
|
-
- `styles.css` with `@import "tailwindcss";` directive
|
|
77
|
-
- Dependencies: `tailwindcss`, `@tailwindcss/postcss`, `postcss`
|
|
78
|
-
|
|
79
|
-
### Angular CLI Integration
|
|
80
|
-
|
|
81
|
-
The tool passes configuration to `ng new`:
|
|
82
|
-
- `--routing`: Enable/disable routing (default: true)
|
|
83
|
-
- `--style=css`: Always uses CSS (Tailwind v4 official approach, no preprocessors)
|
|
84
|
-
- `--ssr`: Server-Side Rendering
|
|
85
|
-
- `--zoneless`: Create zoneless app
|
|
86
|
-
- `--ai-config`: AI tool configurations (multiple flags supported)
|
|
87
|
-
- Always uses: `--skip-git --package-manager=npm --interactive=false`
|
|
88
|
-
|
|
89
|
-
### Starter Template Features
|
|
90
|
-
|
|
91
|
-
The starter template (`lib/templates/starter/`) includes:
|
|
92
|
-
- Standalone Angular 20+ architecture with signals
|
|
93
|
-
- **i18n System**: Full internationalization with English/Arabic translations
|
|
94
|
-
- RTL (Right-to-Left) support for Arabic
|
|
95
|
-
- Language switcher in header (desktop & mobile)
|
|
96
|
-
- Uses `@ngx-translate/core` and `@ngx-translate/http-loader`
|
|
97
|
-
- Translation files in `public/assets/i18n/`
|
|
98
|
-
- Auto-detects browser language with localStorage persistence
|
|
99
|
-
- **Layout System**:
|
|
100
|
-
- Main layout (header with navigation + footer)
|
|
101
|
-
- Auth layout (for login/register pages)
|
|
102
|
-
- **Shared Components**: Button, Card, LoadingSpinner (with Tailwind styling)
|
|
103
|
-
- **Auth UI Pages**: Login, Register, Forgot Password (with form validation)
|
|
104
|
-
- **Example Pages**: Home, About, Contact (with reactive forms and validation)
|
|
105
|
-
- **Core Services**: API service, Auth service, i18n service, Translation service
|
|
106
|
-
- **Routing**: Auth guards, lazy loading examples
|
|
107
|
-
- **TypeScript**: Models, interfaces, strongly-typed services
|
|
108
|
-
- **Assets**: Organized in `public/assets/` (images, i18n, icons)
|
|
109
|
-
- **Path Aliases**: `@core`, `@shared`, `@features` configured in tsconfig.json
|
|
110
|
-
|
|
111
|
-
## File Locations
|
|
112
|
-
|
|
113
|
-
- **CLI entry point**: `bin/create-ng-tailwind.js` - Minimal wrapper that instantiates CLIHandler
|
|
114
|
-
- **Main package config**: `package.json`
|
|
115
|
-
- **Library code**: `lib/` (modular architecture)
|
|
116
|
-
- `lib/cli/index.js` - Main CLIHandler class
|
|
117
|
-
- `lib/managers/` - ProjectManager, TailwindManager, TemplateManager
|
|
118
|
-
- `lib/templates/` - Template definitions (minimal, starter)
|
|
119
|
-
- `lib/utils/` - Logger, helpers, constants
|
|
120
|
-
- **Generated projects**: Use `public/assets/` for assets (Angular 17+ convention, not `src/assets/`)
|
|
121
|
-
|
|
122
|
-
## Publishing
|
|
123
|
-
|
|
124
|
-
Before publishing:
|
|
125
|
-
1. Update version in `package.json`
|
|
126
|
-
2. Update `CHANGELOG.md`
|
|
127
|
-
3. Run `npm publish`
|
|
128
|
-
|
|
129
|
-
See `PUBLISHING.md` for detailed publishing instructions.
|
|
130
|
-
|
|
131
|
-
## Common Development Patterns
|
|
132
|
-
|
|
133
|
-
### Adding a New Template
|
|
134
|
-
|
|
135
|
-
1. Create `lib/templates/your-template/index.js`
|
|
136
|
-
2. Export an object with an `apply(config)` async function:
|
|
137
|
-
```javascript
|
|
138
|
-
module.exports = {
|
|
139
|
-
async apply(config) {
|
|
140
|
-
// config contains: projectName, template, style (always "css"), routing, ssr, zoneless, aiConfig, skipInstall, fullPath
|
|
141
|
-
// Modify the generated Angular project here
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
```
|
|
145
|
-
3. Register in `lib/templates/index.js`
|
|
146
|
-
4. Update CLI prompt choices in `lib/cli/index.js:getTemplateChoice()`
|
|
147
|
-
|
|
148
|
-
### Modifying Angular CLI Behavior
|
|
149
|
-
|
|
150
|
-
Edit `lib/managers/ProjectManager.js:createAngularProject()` to adjust the `ng new` command construction. The method builds a command string with flags based on config options.
|
|
151
|
-
|
|
152
|
-
### Changing Tailwind Configuration
|
|
153
|
-
|
|
154
|
-
- **PostCSS setup**: Edit `lib/managers/TailwindManager.js:createPostCSSConfig()`
|
|
155
|
-
- **Stylesheet imports**: Edit `lib/managers/TailwindManager.js:updateStyles()`
|
|
156
|
-
- **Version/dependencies**: Edit `TailwindManager.js:addTailwindToPackageJson()`
|
|
157
|
-
|
|
158
|
-
### Adding CLI Options
|
|
159
|
-
|
|
160
|
-
1. Add option to `lib/cli/index.js:parseOptions()` using commander
|
|
161
|
-
2. Add prompt in appropriate method (e.g., `getConfigurationOptions()`)
|
|
162
|
-
3. Include in config object in `buildConfiguration()`
|
|
163
|
-
4. Use in managers or templates via `config` parameter
|
|
164
|
-
|
|
165
|
-
## Important Constraints and Best Practices
|
|
166
|
-
|
|
167
|
-
- **Node.js 18+** required (specified in package.json engines)
|
|
168
|
-
- **Angular CLI**: Relies on `@angular/cli@latest` being available via npx
|
|
169
|
-
- **Module System**: Uses CommonJS (not ESM) for Node.js compatibility
|
|
170
|
-
- **Tailwind v4 Dependencies**: Must be in `dependencies`, NOT `devDependencies` (required at runtime)
|
|
171
|
-
- **CSS-Only Approach**: Only CSS is supported (Tailwind v4 no longer supports SCSS/Sass/Less preprocessors due to LightningCSS)
|
|
172
|
-
- **CLI Output**: All user-facing messages must use the Logger utility (`lib/utils/logger.js`) for consistent styling with chalk, ora spinners, and inquirer prompts
|
|
173
|
-
- **Stylesheet Format**: Always uses CSS (`config.style` is always `"css"`) - `TailwindManager.js:updateStyles()` writes to `styles.css`
|
|
174
|
-
- **Interactive Mode**: Default behavior - CLI prompts for options not provided via flags
|
|
175
|
-
- **Fast Mode**: All prompts can be skipped by providing complete CLI flags
|
|
176
|
-
- **AI Config**: Handled by Angular CLI's `--ai-config` flag, our CLI just passes it through (supports multiple tools)
|
|
177
|
-
- **Assets Location**: Generated projects use `public/assets/` (Angular 17+ convention), not `src/assets/`
|
|
178
|
-
- **Angular v20 ESLint Fix**: The starter template's ESLint config includes `"suffixes": ["Component", "App"]` for the `@angular-eslint/component-class-suffix` rule to support Angular v20's new `app.ts` naming convention (class `App` instead of `AppComponent`)
|