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.
@@ -27,6 +27,21 @@ const {
27
27
  createTooltipDirective,
28
28
  } = require('./ui-features');
29
29
 
30
+ const {
31
+ createSEOService,
32
+ createStructuredDataUtil,
33
+ createRobotsTxt,
34
+ } = require('./seo-features');
35
+
36
+ const {
37
+ createDefaultOGImage,
38
+ createFavicon,
39
+ createAppleTouchIcon,
40
+ createAndroidIcon,
41
+ createAndroidIconLarge,
42
+ createLogo,
43
+ } = require('./seo-assets');
44
+
30
45
  const starter = {
31
46
  info: {
32
47
  name: 'Starter',
@@ -37,6 +52,10 @@ const starter = {
37
52
  'HTTP Interceptors (Auth, Error, Loading, Caching)',
38
53
  'i18n translation support (English & Arabic) with RTL',
39
54
  'Language switcher in header (desktop & mobile)',
55
+ 'Comprehensive SEO service (meta tags, Open Graph, Twitter Cards)',
56
+ 'Structured data (JSON-LD) for rich search results',
57
+ 'Multi-language SEO with hreflang tags',
58
+ 'robots.txt and favicon set included',
40
59
  'Toast/Notification system with auto-dismiss',
41
60
  'Modal/Dialog system with confirm & alert',
42
61
  'Essential UI components (Button, Card, Spinner, Toast, Modal)',
@@ -46,7 +65,7 @@ const starter = {
46
65
  'Authentication UI (Login, Register, Forgot Password)',
47
66
  '3 example pages (Home, About, Contact)',
48
67
  'Reactive forms with validation',
49
- 'API, Auth, Storage, Loading, Cache services',
68
+ 'API, Auth, Storage, Loading, Cache, SEO services',
50
69
  'TypeScript interfaces and models',
51
70
  'Responsive Tailwind design system',
52
71
  'ESLint + Prettier + Husky pre-commit hooks',
@@ -74,6 +93,7 @@ const starter = {
74
93
  await this.createDirectoryStructure(config);
75
94
  await this.createEnvironments(config);
76
95
  await this.createCoreServices(config);
96
+ await this.createSEOFeatures(config);
77
97
  await this.createHttpInterceptors(config);
78
98
  await this.createI18n(config);
79
99
  await this.createGuards(config);
@@ -97,17 +117,18 @@ const starter = {
97
117
  await this.setupLinting(config);
98
118
  await this.setupPWA(config);
99
119
  await this.formatCode(config);
100
-
120
+
101
121
  // Stop spinner and show summary
102
122
  if (spinner) spinner.stop();
103
-
123
+
104
124
  // Show minimal, professional summary
105
125
  console.log('');
106
126
  completeStep('Angular 20+ project created');
107
127
  completeStep('Tailwind CSS v4 configured');
108
128
  completeStep('i18n translation support (English & Arabic)');
129
+ completeStep('SEO optimization (meta tags, Open Graph, structured data)');
109
130
  completeStep('HTTP interceptors (Auth, Error, Loading, Cache)');
110
- completeStep('Core services (8 services)');
131
+ completeStep('Core services (9 services including SEO)');
111
132
  completeStep('UI components (Button, Card, Toast, Modal, Spinner)');
112
133
  completeStep('Utility pipes & directives');
113
134
  completeStep('Authentication pages & guards');
@@ -123,6 +144,7 @@ const starter = {
123
144
  'src/app/core/guards',
124
145
  'src/app/core/i18n',
125
146
  'src/app/core/interceptors',
147
+ 'src/app/core/utils',
126
148
  'src/app/shared/components/button',
127
149
  'src/app/shared/components/card',
128
150
  'src/app/shared/components/loading-spinner',
@@ -142,8 +164,7 @@ const starter = {
142
164
  'src/app/layout/auth',
143
165
  'public/assets/images',
144
166
  'public/assets/i18n',
145
- 'public/images',
146
- 'public/styles',
167
+ 'public/assets/icons',
147
168
  ];
148
169
 
149
170
  for (const dir of directories) {
@@ -545,6 +566,70 @@ export class StorageService {
545
566
  );
546
567
  },
547
568
 
569
+ async createSEOFeatures(config) {
570
+ // Create SEO Service
571
+ const seoService = createSEOService();
572
+ await fs.writeFile(
573
+ path.join(config.fullPath, 'src/app/core/services/seo.service.ts'),
574
+ seoService
575
+ );
576
+
577
+ // Create Structured Data Utility
578
+ const structuredDataUtil = createStructuredDataUtil();
579
+ await fs.writeFile(
580
+ path.join(config.fullPath, 'src/app/core/utils/structured-data.ts'),
581
+ structuredDataUtil
582
+ );
583
+
584
+ // Create robots.txt in public folder
585
+ const robotsTxt = createRobotsTxt();
586
+ await fs.writeFile(
587
+ path.join(config.fullPath, 'public/robots.txt'),
588
+ robotsTxt
589
+ );
590
+
591
+ // Create default OG image (SVG as placeholder)
592
+ const ogImage = createDefaultOGImage(config.projectName);
593
+ await fs.writeFile(
594
+ path.join(config.fullPath, 'public/assets/images/og-default.svg'),
595
+ ogImage
596
+ );
597
+
598
+ // Create favicon
599
+ const favicon = createFavicon(config.projectName);
600
+ await fs.writeFile(
601
+ path.join(config.fullPath, 'public/favicon.svg'),
602
+ favicon
603
+ );
604
+
605
+ // Create Apple Touch Icon (SVG as placeholder)
606
+ const appleTouchIcon = createAppleTouchIcon(config.projectName);
607
+ await fs.writeFile(
608
+ path.join(config.fullPath, 'public/assets/icons/apple-touch-icon.svg'),
609
+ appleTouchIcon
610
+ );
611
+
612
+ // Create Android Chrome icons (SVG as placeholder)
613
+ const androidIcon = createAndroidIcon(config.projectName);
614
+ await fs.writeFile(
615
+ path.join(config.fullPath, 'public/assets/icons/android-chrome-192x192.svg'),
616
+ androidIcon
617
+ );
618
+
619
+ const androidIconLarge = createAndroidIconLarge(config.projectName);
620
+ await fs.writeFile(
621
+ path.join(config.fullPath, 'public/assets/icons/android-chrome-512x512.svg'),
622
+ androidIconLarge
623
+ );
624
+
625
+ // Create logo
626
+ const logo = createLogo(config.projectName);
627
+ await fs.writeFile(
628
+ path.join(config.fullPath, 'public/assets/images/logo.svg'),
629
+ logo
630
+ );
631
+ },
632
+
548
633
  async createHttpInterceptors(config) {
549
634
  // Create all HTTP interceptors
550
635
  await createAuthInterceptor(config);
@@ -2734,7 +2819,7 @@ export class ForgotPasswordComponent {
2734
2819
  },
2735
2820
 
2736
2821
  async createHomeComponent(config) {
2737
- const homeComponentTs = `import { Component, inject } from '@angular/core';
2822
+ const homeComponentTs = `import { Component, OnInit, inject } from '@angular/core';
2738
2823
  import { CommonModule } from '@angular/common';
2739
2824
  import { RouterModule } from '@angular/router';
2740
2825
  import { NgIconComponent, provideIcons } from '@ng-icons/core';
@@ -2757,6 +2842,7 @@ import { ButtonComponent } from '@shared/components/button/button.component';
2757
2842
  import { CardComponent } from '@shared/components/card/card.component';
2758
2843
  import { ToastService } from '@core/services/toast.service';
2759
2844
  import { ModalService } from '@core/services/modal.service';
2845
+ import { SeoService } from '@core/services/seo.service';
2760
2846
 
2761
2847
  @Component({
2762
2848
  selector: 'app-home',
@@ -2778,11 +2864,22 @@ import { ModalService } from '@core/services/modal.service';
2778
2864
  })],
2779
2865
  templateUrl: './home.component.html'
2780
2866
  })
2781
- export class HomeComponent {
2867
+ export class HomeComponent implements OnInit {
2782
2868
  protected readonly projectName = '${config.projectName}';
2783
2869
 
2784
2870
  private toast = inject(ToastService);
2785
2871
  private modal = inject(ModalService);
2872
+ private seo = inject(SeoService);
2873
+
2874
+ ngOnInit(): void {
2875
+ // Set SEO meta tags for Home page
2876
+ this.seo.updateMeta({
2877
+ title: 'Home',
2878
+ description: 'Welcome to ${config.projectName} - A modern Angular application with Tailwind CSS, SEO optimization, and i18n support.',
2879
+ keywords: 'angular, tailwind, seo, i18n, typescript',
2880
+ ogType: 'website'
2881
+ });
2882
+ }
2786
2883
 
2787
2884
  // Toast Examples
2788
2885
  showSuccessToast(): void {
@@ -3350,7 +3447,7 @@ export class HomeComponent {
3350
3447
  },
3351
3448
 
3352
3449
  async createAboutComponent(config) {
3353
- const aboutComponentTs = `import { Component } from '@angular/core';
3450
+ const aboutComponentTs = `import { Component, OnInit, inject } from '@angular/core';
3354
3451
  import { CommonModule } from '@angular/common';
3355
3452
  import { RouterModule } from '@angular/router';
3356
3453
  import { NgIconComponent, provideIcons } from '@ng-icons/core';
@@ -3364,6 +3461,7 @@ import {
3364
3461
  } from '@ng-icons/heroicons/outline';
3365
3462
  import { TranslateModule } from '@ngx-translate/core';
3366
3463
  import { ButtonComponent } from '@shared/components/button/button.component';
3464
+ import { SeoService } from '@core/services/seo.service';
3367
3465
 
3368
3466
  @Component({
3369
3467
  selector: 'app-about',
@@ -3379,8 +3477,19 @@ import { ButtonComponent } from '@shared/components/button/button.component';
3379
3477
  })],
3380
3478
  templateUrl: './about.component.html'
3381
3479
  })
3382
- export class AboutComponent {
3480
+ export class AboutComponent implements OnInit {
3383
3481
  protected readonly projectName = '${config.projectName}';
3482
+ private seo = inject(SeoService);
3483
+
3484
+ ngOnInit(): void {
3485
+ // Set SEO meta tags for About page
3486
+ this.seo.updateMeta({
3487
+ title: 'About Us',
3488
+ description: 'Learn more about ${config.projectName} and our mission to create modern web applications.',
3489
+ keywords: 'about, team, mission, company',
3490
+ ogType: 'website'
3491
+ });
3492
+ }
3384
3493
  }`;
3385
3494
 
3386
3495
  const aboutComponentHtml = `<!-- Hero Section -->
@@ -3939,49 +4048,13 @@ npx lint-staged
3939
4048
  description: `${config.projectName} - Built with Angular and Tailwind CSS`,
3940
4049
  icons: [
3941
4050
  {
3942
- src: 'assets/icons/icon-72x72.svg',
3943
- sizes: '72x72',
3944
- type: 'image/svg+xml',
3945
- purpose: 'maskable any',
3946
- },
3947
- {
3948
- src: 'assets/icons/icon-96x96.svg',
3949
- sizes: '96x96',
3950
- type: 'image/svg+xml',
3951
- purpose: 'maskable any',
3952
- },
3953
- {
3954
- src: 'assets/icons/icon-128x128.svg',
3955
- sizes: '128x128',
3956
- type: 'image/svg+xml',
3957
- purpose: 'maskable any',
3958
- },
3959
- {
3960
- src: 'assets/icons/icon-144x144.svg',
3961
- sizes: '144x144',
3962
- type: 'image/svg+xml',
3963
- purpose: 'maskable any',
3964
- },
3965
- {
3966
- src: 'assets/icons/icon-152x152.svg',
3967
- sizes: '152x152',
3968
- type: 'image/svg+xml',
3969
- purpose: 'maskable any',
3970
- },
3971
- {
3972
- src: 'assets/icons/icon-192x192.svg',
4051
+ src: 'assets/icons/android-chrome-192x192.svg',
3973
4052
  sizes: '192x192',
3974
4053
  type: 'image/svg+xml',
3975
4054
  purpose: 'maskable any',
3976
4055
  },
3977
4056
  {
3978
- src: 'assets/icons/icon-384x384.svg',
3979
- sizes: '384x384',
3980
- type: 'image/svg+xml',
3981
- purpose: 'maskable any',
3982
- },
3983
- {
3984
- src: 'assets/icons/icon-512x512.svg',
4057
+ src: 'assets/icons/android-chrome-512x512.svg',
3985
4058
  sizes: '512x512',
3986
4059
  type: 'image/svg+xml',
3987
4060
  purpose: 'maskable any',
@@ -0,0 +1,141 @@
1
+ /**
2
+ * SEO Assets - SVG placeholders for OG images and favicons
3
+ * These are temporary placeholders that users should replace with their own branding
4
+ */
5
+
6
+ /**
7
+ * Generate default Open Graph image (1200x630px - recommended size)
8
+ */
9
+ function createDefaultOGImage(projectName) {
10
+ return `<svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
11
+ <defs>
12
+ <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
13
+ <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
14
+ <stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
15
+ </linearGradient>
16
+ </defs>
17
+ <rect width="1200" height="630" fill="url(#grad)"/>
18
+ <text x="600" y="280" font-family="Arial, sans-serif" font-size="72" font-weight="bold" fill="white" text-anchor="middle">
19
+ ${projectName}
20
+ </text>
21
+ <text x="600" y="350" font-family="Arial, sans-serif" font-size="32" fill="white" text-anchor="middle" opacity="0.9">
22
+ Modern Angular Application
23
+ </text>
24
+ <text x="600" y="550" font-family="Arial, sans-serif" font-size="20" fill="white" text-anchor="middle" opacity="0.7">
25
+ Replace this image with your own branding
26
+ </text>
27
+ </svg>`;
28
+ }
29
+
30
+ /**
31
+ * Generate favicon (32x32px)
32
+ */
33
+ function createFavicon(projectName) {
34
+ // Get first letter of project name
35
+ const letter = projectName.charAt(0).toUpperCase();
36
+
37
+ return `<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg">
38
+ <defs>
39
+ <linearGradient id="faviconGrad" x1="0%" y1="0%" x2="100%" y2="100%">
40
+ <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
41
+ <stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
42
+ </linearGradient>
43
+ </defs>
44
+ <rect width="32" height="32" rx="6" fill="url(#faviconGrad)"/>
45
+ <text x="16" y="23" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="white" text-anchor="middle">
46
+ ${letter}
47
+ </text>
48
+ </svg>`;
49
+ }
50
+
51
+ /**
52
+ * Generate Apple Touch Icon (180x180px)
53
+ */
54
+ function createAppleTouchIcon(projectName) {
55
+ const letter = projectName.charAt(0).toUpperCase();
56
+
57
+ return `<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg">
58
+ <defs>
59
+ <linearGradient id="appleGrad" x1="0%" y1="0%" x2="100%" y2="100%">
60
+ <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
61
+ <stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
62
+ </linearGradient>
63
+ </defs>
64
+ <rect width="180" height="180" rx="40" fill="url(#appleGrad)"/>
65
+ <text x="90" y="130" font-family="Arial, sans-serif" font-size="100" font-weight="bold" fill="white" text-anchor="middle">
66
+ ${letter}
67
+ </text>
68
+ </svg>`;
69
+ }
70
+
71
+ /**
72
+ * Generate Android Chrome icon (192x192px)
73
+ */
74
+ function createAndroidIcon(projectName) {
75
+ const letter = projectName.charAt(0).toUpperCase();
76
+
77
+ return `<svg width="192" height="192" xmlns="http://www.w3.org/2000/svg">
78
+ <defs>
79
+ <linearGradient id="androidGrad" x1="0%" y1="0%" x2="100%" y2="100%">
80
+ <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
81
+ <stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
82
+ </linearGradient>
83
+ </defs>
84
+ <rect width="192" height="192" rx="48" fill="url(#androidGrad)"/>
85
+ <text x="96" y="140" font-family="Arial, sans-serif" font-size="110" font-weight="bold" fill="white" text-anchor="middle">
86
+ ${letter}
87
+ </text>
88
+ </svg>`;
89
+ }
90
+
91
+ /**
92
+ * Generate large Android Chrome icon (512x512px)
93
+ */
94
+ function createAndroidIconLarge(projectName) {
95
+ const letter = projectName.charAt(0).toUpperCase();
96
+
97
+ return `<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg">
98
+ <defs>
99
+ <linearGradient id="androidLargeGrad" x1="0%" y1="0%" x2="100%" y2="100%">
100
+ <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
101
+ <stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
102
+ </linearGradient>
103
+ </defs>
104
+ <rect width="512" height="512" rx="128" fill="url(#androidLargeGrad)"/>
105
+ <text x="256" y="370" font-family="Arial, sans-serif" font-size="300" font-weight="bold" fill="white" text-anchor="middle">
106
+ ${letter}
107
+ </text>
108
+ </svg>`;
109
+ }
110
+
111
+ /**
112
+ * Generate logo SVG
113
+ */
114
+ function createLogo(projectName) {
115
+ const letter = projectName.charAt(0).toUpperCase();
116
+
117
+ return `<svg width="200" height="60" xmlns="http://www.w3.org/2000/svg">
118
+ <defs>
119
+ <linearGradient id="logoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
120
+ <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
121
+ <stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
122
+ </linearGradient>
123
+ </defs>
124
+ <rect width="50" height="50" x="5" y="5" rx="10" fill="url(#logoGrad)"/>
125
+ <text x="30" y="42" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white" text-anchor="middle">
126
+ ${letter}
127
+ </text>
128
+ <text x="70" y="40" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="#1f2937">
129
+ ${projectName}
130
+ </text>
131
+ </svg>`;
132
+ }
133
+
134
+ module.exports = {
135
+ createDefaultOGImage,
136
+ createFavicon,
137
+ createAppleTouchIcon,
138
+ createAndroidIcon,
139
+ createAndroidIconLarge,
140
+ createLogo,
141
+ };