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,243 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Create Button Component
6
+ */
7
+ async function createButtonComponent(config) {
8
+ const buttonComponent = `import { Component, Input, Output, EventEmitter } from '@angular/core';
9
+
10
+ export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
11
+ export type ButtonSize = 'sm' | 'md' | 'lg';
12
+
13
+ @Component({
14
+ selector: 'app-button',
15
+ standalone: true,
16
+ imports: [],
17
+ template: \`
18
+ <button
19
+ [class]="buttonClasses"
20
+ [disabled]="disabled || loading"
21
+ (click)="handleClick($event)"
22
+ [type]="type">
23
+
24
+ @if (loading) {
25
+ <svg class="animate-spin -ml-1 mr-3 h-4 w-4" fill="none" viewBox="0 0 24 24">
26
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"></circle>
27
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
28
+ </svg>
29
+ }
30
+
31
+ <ng-content></ng-content>
32
+ </button>
33
+ \`
34
+ })
35
+ export class ButtonComponent {
36
+ @Input() variant: ButtonVariant = 'primary';
37
+ @Input() size: ButtonSize = 'md';
38
+ @Input() disabled = false;
39
+ @Input() loading = false;
40
+ @Input() type: 'button' | 'submit' | 'reset' = 'button';
41
+ @Input() fullWidth = false;
42
+
43
+ @Output() clicked = new EventEmitter<Event>();
44
+
45
+ get buttonClasses(): string {
46
+ const baseClasses = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50';
47
+
48
+ const sizeClasses = {
49
+ sm: 'text-sm px-3 py-2 h-9',
50
+ md: 'text-sm px-4 py-2 h-10',
51
+ lg: 'text-base px-8 py-3 h-11'
52
+ };
53
+
54
+ const variantClasses = {
55
+ primary: 'bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500',
56
+ secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-400',
57
+ outline: 'border-2 border-primary-600 text-primary-600 hover:bg-primary-600 hover:text-white focus:ring-primary-500',
58
+ ghost: 'hover:bg-gray-100 hover:text-gray-900 focus:ring-gray-400',
59
+ danger: 'bg-danger-600 text-white hover:bg-danger-700 focus:ring-danger-500'
60
+ };
61
+
62
+ const widthClass = this.fullWidth ? 'w-full' : '';
63
+
64
+ return \`\${baseClasses} \${sizeClasses[this.size]} \${variantClasses[this.variant]} \${widthClass}\`.trim();
65
+ }
66
+
67
+ handleClick(event: Event): void {
68
+ if (!this.disabled && !this.loading) {
69
+ this.clicked.emit(event);
70
+ }
71
+ }
72
+ }`;
73
+
74
+ await fs.writeFile(
75
+ path.join(config.fullPath, 'src/app/shared/components/button/button.component.ts'),
76
+ buttonComponent
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Create Card Component
82
+ */
83
+ async function createCardComponent(config) {
84
+ const cardComponent = `import { Component, Input } from '@angular/core';
85
+
86
+ @Component({
87
+ selector: 'app-card',
88
+ standalone: true,
89
+ imports: [],
90
+ template: \`
91
+ <div [class]="cardClasses">
92
+ @if (title || subtitle) {
93
+ <div class="px-6 py-4 border-b border-gray-200">
94
+ @if (title) {
95
+ <h3 class="text-lg font-semibold text-gray-900">
96
+ {{ title }}
97
+ </h3>
98
+ }
99
+ @if (subtitle) {
100
+ <p class="text-sm text-gray-600 mt-1">
101
+ {{ subtitle }}
102
+ </p>
103
+ }
104
+ </div>
105
+ }
106
+
107
+ <div [class]="contentClasses">
108
+ <ng-content></ng-content>
109
+ </div>
110
+
111
+ @if (hasFooter) {
112
+ <div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
113
+ <ng-content select="[slot=footer]"></ng-content>
114
+ </div>
115
+ }
116
+ </div>
117
+ \`
118
+ })
119
+ export class CardComponent {
120
+ @Input() title?: string;
121
+ @Input() subtitle?: string;
122
+ @Input() padding = true;
123
+ @Input() shadow = true;
124
+ @Input() hover = false;
125
+ @Input() hasFooter = false;
126
+
127
+ get cardClasses(): string {
128
+ const baseClasses = 'bg-white border border-gray-200 rounded-lg overflow-hidden';
129
+ const shadowClasses = this.shadow ? 'shadow-sm' : '';
130
+ const hoverClasses = this.hover ? 'hover:shadow-md transition-shadow duration-200' : '';
131
+
132
+ return \`\${baseClasses} \${shadowClasses} \${hoverClasses}\`.trim();
133
+ }
134
+
135
+ get contentClasses(): string {
136
+ return this.padding ? 'p-6' : '';
137
+ }
138
+ }`;
139
+
140
+ await fs.writeFile(
141
+ path.join(config.fullPath, 'src/app/shared/components/card/card.component.ts'),
142
+ cardComponent
143
+ );
144
+ }
145
+
146
+ /**
147
+ * Create Loading Spinner Component
148
+ */
149
+ async function createLoadingSpinnerComponent(config) {
150
+ const spinnerComponent = `import { Component, Input } from '@angular/core';
151
+
152
+ export type SpinnerSize = 'sm' | 'md' | 'lg' | 'xl';
153
+
154
+ @Component({
155
+ selector: 'app-loading-spinner',
156
+ standalone: true,
157
+ imports: [],
158
+ template: \`
159
+ <div [class]="containerClasses">
160
+ <svg [class]="spinnerClasses" fill="none" viewBox="0 0 24 24">
161
+ <circle
162
+ cx="12" cy="12" r="10"
163
+ stroke="currentColor"
164
+ stroke-width="4"
165
+ class="opacity-25">
166
+ </circle>
167
+ <path
168
+ class="opacity-75"
169
+ fill="currentColor"
170
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
171
+ </path>
172
+ </svg>
173
+ @if (text) {
174
+ <span [class]="textClasses">{{ text }}</span>
175
+ }
176
+ </div>
177
+ \`
178
+ })
179
+ export class LoadingSpinnerComponent {
180
+ @Input() size: SpinnerSize = 'md';
181
+ @Input() text?: string;
182
+ @Input() centered = false;
183
+ @Input() color = 'primary';
184
+
185
+ private readonly sizeClasses = {
186
+ sm: 'h-4 w-4',
187
+ md: 'h-8 w-8',
188
+ lg: 'h-12 w-12',
189
+ xl: 'h-16 w-16'
190
+ } as const;
191
+
192
+ private readonly colorClasses = {
193
+ primary: 'text-primary-600',
194
+ secondary: 'text-secondary-600',
195
+ accent: 'text-accent-600',
196
+ success: 'text-success-600',
197
+ danger: 'text-danger-600',
198
+ warning: 'text-warning-600',
199
+ info: 'text-info-600',
200
+ gray: 'text-gray-600',
201
+ white: 'text-white'
202
+ } as const;
203
+
204
+ protected readonly textClasses = 'text-sm text-gray-600';
205
+
206
+ get containerClasses(): string {
207
+ const baseClasses = 'flex items-center';
208
+ const centerClasses = this.centered ? 'justify-center' : '';
209
+ const directionClasses = this.text ? 'flex-col space-y-2' : '';
210
+
211
+ return \`\${baseClasses} \${centerClasses} \${directionClasses}\`.trim();
212
+ }
213
+
214
+ get spinnerClasses(): string {
215
+ const baseClasses = 'animate-spin';
216
+ return \`\${baseClasses} \${this.sizeClasses[this.size]} \${this.colorClasses[this.color as keyof typeof this.colorClasses] || this.colorClasses.primary}\`.trim();
217
+ }
218
+ }`;
219
+
220
+ await fs.writeFile(
221
+ path.join(
222
+ config.fullPath,
223
+ 'src/app/shared/components/loading-spinner/loading-spinner.component.ts'
224
+ ),
225
+ spinnerComponent
226
+ );
227
+ }
228
+
229
+ /**
230
+ * Create all shared components
231
+ */
232
+ async function createSharedComponents(config) {
233
+ await createButtonComponent(config);
234
+ await createCardComponent(config);
235
+ await createLoadingSpinnerComponent(config);
236
+ }
237
+
238
+ module.exports = {
239
+ createButtonComponent,
240
+ createCardComponent,
241
+ createLoadingSpinnerComponent,
242
+ createSharedComponents,
243
+ };
@@ -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;