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