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
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SEO Features for Starter Template
|
|
6
|
+
* Simple and focused SEO implementation - developers can extend as needed
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create simplified SEO Service (only essential features)
|
|
11
|
+
*/
|
|
12
|
+
function createSEOService() {
|
|
13
|
+
return `import { Injectable, inject } from '@angular/core';
|
|
14
|
+
import { Meta, Title } from '@angular/platform-browser';
|
|
15
|
+
import { Router, NavigationEnd } from '@angular/router';
|
|
16
|
+
import { DOCUMENT } from '@angular/common';
|
|
17
|
+
import { filter } from 'rxjs/operators';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* SEO Service - Simple SEO management for Angular apps
|
|
21
|
+
*
|
|
22
|
+
* Includes:
|
|
23
|
+
* - Dynamic meta tags (title, description, keywords)
|
|
24
|
+
* - Open Graph tags for social sharing (Facebook, LinkedIn)
|
|
25
|
+
* - Twitter Card tags
|
|
26
|
+
* - Canonical URLs (auto-updated on route changes)
|
|
27
|
+
* - Structured data (JSON-LD) support
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // In any component
|
|
31
|
+
* constructor(private seo: SeoService) {}
|
|
32
|
+
*
|
|
33
|
+
* ngOnInit() {
|
|
34
|
+
* this.seo.updateMeta({
|
|
35
|
+
* title: 'About Us',
|
|
36
|
+
* description: 'Learn more about our company',
|
|
37
|
+
* keywords: 'about, company, team'
|
|
38
|
+
* });
|
|
39
|
+
* }
|
|
40
|
+
*/
|
|
41
|
+
@Injectable({
|
|
42
|
+
providedIn: 'root'
|
|
43
|
+
})
|
|
44
|
+
export class SeoService {
|
|
45
|
+
private meta = inject(Meta);
|
|
46
|
+
private title = inject(Title);
|
|
47
|
+
private router = inject(Router);
|
|
48
|
+
private document = inject(DOCUMENT);
|
|
49
|
+
|
|
50
|
+
// Default configuration - update these values for your app
|
|
51
|
+
private readonly config = {
|
|
52
|
+
siteName: 'My Angular App', // TODO: Update with your site name
|
|
53
|
+
siteUrl: 'https://example.com', // TODO: Update with your production URL
|
|
54
|
+
titleSuffix: ' | My Angular App',
|
|
55
|
+
defaultDescription: 'A modern Angular application built with best practices and SEO optimization.',
|
|
56
|
+
defaultImage: '/assets/images/og-default.svg', // TODO: Replace with JPG/PNG for better compatibility
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
constructor() {
|
|
60
|
+
// Auto-update canonical URL on route changes
|
|
61
|
+
this.router.events
|
|
62
|
+
.pipe(filter((event) => event instanceof NavigationEnd))
|
|
63
|
+
.subscribe(() => {
|
|
64
|
+
this.updateCanonicalUrl();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Update SEO meta tags for a page
|
|
70
|
+
*/
|
|
71
|
+
updateMeta(options: {
|
|
72
|
+
title?: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
keywords?: string;
|
|
75
|
+
ogImage?: string;
|
|
76
|
+
ogType?: 'website' | 'article';
|
|
77
|
+
}): void {
|
|
78
|
+
// Update title
|
|
79
|
+
if (options.title) {
|
|
80
|
+
const fullTitle = options.title + this.config.titleSuffix;
|
|
81
|
+
this.title.setTitle(fullTitle);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Update meta tags
|
|
85
|
+
const description = options.description || this.config.defaultDescription;
|
|
86
|
+
const image = this.getFullUrl(options.ogImage || this.config.defaultImage);
|
|
87
|
+
|
|
88
|
+
this.meta.updateTag({ name: 'description', content: description });
|
|
89
|
+
if (options.keywords) {
|
|
90
|
+
this.meta.updateTag({ name: 'keywords', content: options.keywords });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Open Graph tags
|
|
94
|
+
this.meta.updateTag({ property: 'og:title', content: options.title || this.config.siteName });
|
|
95
|
+
this.meta.updateTag({ property: 'og:description', content: description });
|
|
96
|
+
this.meta.updateTag({ property: 'og:image', content: image });
|
|
97
|
+
this.meta.updateTag({ property: 'og:url', content: this.getCurrentUrl() });
|
|
98
|
+
this.meta.updateTag({ property: 'og:type', content: options.ogType || 'website' });
|
|
99
|
+
this.meta.updateTag({ property: 'og:site_name', content: this.config.siteName });
|
|
100
|
+
|
|
101
|
+
// Twitter Card tags
|
|
102
|
+
this.meta.updateTag({ property: 'twitter:card', content: 'summary_large_image' });
|
|
103
|
+
this.meta.updateTag({ property: 'twitter:title', content: options.title || this.config.siteName });
|
|
104
|
+
this.meta.updateTag({ property: 'twitter:description', content: description });
|
|
105
|
+
this.meta.updateTag({ property: 'twitter:image', content: image });
|
|
106
|
+
|
|
107
|
+
// Update canonical URL
|
|
108
|
+
this.updateCanonicalUrl();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Add structured data (JSON-LD) to the page
|
|
113
|
+
* Use with helper functions from @core/utils/structured-data
|
|
114
|
+
*/
|
|
115
|
+
addStructuredData(data: Record<string, unknown>): void {
|
|
116
|
+
let script: HTMLScriptElement | null = this.document.querySelector('script[type="application/ld+json"]');
|
|
117
|
+
|
|
118
|
+
if (!script) {
|
|
119
|
+
script = this.document.createElement('script');
|
|
120
|
+
script.type = 'application/ld+json';
|
|
121
|
+
this.document.head.appendChild(script);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
script.textContent = JSON.stringify(data);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Update canonical URL based on current route
|
|
129
|
+
*/
|
|
130
|
+
private updateCanonicalUrl(): void {
|
|
131
|
+
const currentUrl = this.getCurrentUrl();
|
|
132
|
+
let link: HTMLLinkElement | null = this.document.querySelector('link[rel="canonical"]');
|
|
133
|
+
|
|
134
|
+
if (!link) {
|
|
135
|
+
link = this.document.createElement('link');
|
|
136
|
+
link.setAttribute('rel', 'canonical');
|
|
137
|
+
this.document.head.appendChild(link);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
link.setAttribute('href', currentUrl);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get current full URL
|
|
145
|
+
*/
|
|
146
|
+
private getCurrentUrl(): string {
|
|
147
|
+
return this.config.siteUrl + this.router.url;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Convert relative URL to absolute URL
|
|
152
|
+
*/
|
|
153
|
+
private getFullUrl(url: string): string {
|
|
154
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
155
|
+
return url;
|
|
156
|
+
}
|
|
157
|
+
return this.config.siteUrl + url;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Create simplified Structured Data Utility (only essential schemas)
|
|
165
|
+
*/
|
|
166
|
+
function createStructuredDataUtil() {
|
|
167
|
+
return `/**
|
|
168
|
+
* Structured Data Utility - Simple Schema.org helpers
|
|
169
|
+
*
|
|
170
|
+
* Provides basic schemas for SEO. Add more schemas as needed.
|
|
171
|
+
*
|
|
172
|
+
* @see https://schema.org
|
|
173
|
+
* @see https://developers.google.com/search/docs/appearance/structured-data
|
|
174
|
+
*/
|
|
175
|
+
|
|
176
|
+
export interface Organization {
|
|
177
|
+
name: string;
|
|
178
|
+
url: string;
|
|
179
|
+
logo: string;
|
|
180
|
+
description?: string;
|
|
181
|
+
sameAs?: string[]; // Social media profiles
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface WebSite {
|
|
185
|
+
name: string;
|
|
186
|
+
url: string;
|
|
187
|
+
description?: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generate Organization structured data
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* const orgData = createOrganizationSchema({
|
|
195
|
+
* name: 'My Company',
|
|
196
|
+
* url: 'https://example.com',
|
|
197
|
+
* logo: '/assets/images/logo.svg',
|
|
198
|
+
* sameAs: ['https://twitter.com/mycompany']
|
|
199
|
+
* });
|
|
200
|
+
*/
|
|
201
|
+
export function createOrganizationSchema(org: Organization): Record<string, unknown> {
|
|
202
|
+
const schema: Record<string, unknown> = {
|
|
203
|
+
'@context': 'https://schema.org',
|
|
204
|
+
'@type': 'Organization',
|
|
205
|
+
name: org.name,
|
|
206
|
+
url: org.url,
|
|
207
|
+
logo: org.logo,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
if (org.description) {
|
|
211
|
+
schema['description'] = org.description;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (org.sameAs && org.sameAs.length > 0) {
|
|
215
|
+
schema['sameAs'] = org.sameAs;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return schema;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Generate WebSite structured data
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* const siteData = createWebSiteSchema({
|
|
226
|
+
* name: 'My Website',
|
|
227
|
+
* url: 'https://example.com',
|
|
228
|
+
* description: 'A modern web application'
|
|
229
|
+
* });
|
|
230
|
+
*/
|
|
231
|
+
export function createWebSiteSchema(site: WebSite): Record<string, unknown> {
|
|
232
|
+
const schema: Record<string, unknown> = {
|
|
233
|
+
'@context': 'https://schema.org',
|
|
234
|
+
'@type': 'WebSite',
|
|
235
|
+
name: site.name,
|
|
236
|
+
url: site.url,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
if (site.description) {
|
|
240
|
+
schema['description'] = site.description;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return schema;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* NEED MORE SCHEMAS?
|
|
248
|
+
*
|
|
249
|
+
* You can easily add more schemas as needed:
|
|
250
|
+
* - Article - for blog posts
|
|
251
|
+
* - Product - for e-commerce
|
|
252
|
+
* - BreadcrumbList - for navigation
|
|
253
|
+
* - FAQ - for FAQ pages
|
|
254
|
+
* - Person - for author profiles
|
|
255
|
+
*
|
|
256
|
+
* See: https://schema.org/docs/full.html
|
|
257
|
+
*/
|
|
258
|
+
`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Create robots.txt for Angular apps
|
|
263
|
+
*/
|
|
264
|
+
function createRobotsTxt() {
|
|
265
|
+
return `# robots.txt for Angular Application
|
|
266
|
+
# Allow all search engines to crawl the site
|
|
267
|
+
|
|
268
|
+
User-agent: *
|
|
269
|
+
Allow: /
|
|
270
|
+
|
|
271
|
+
# Disallow admin areas (if any)
|
|
272
|
+
# Disallow: /admin/
|
|
273
|
+
# Disallow: /api/
|
|
274
|
+
|
|
275
|
+
# Disallow Angular assets that shouldn't be indexed
|
|
276
|
+
Disallow: /*.js$
|
|
277
|
+
Disallow: /*.css$
|
|
278
|
+
Disallow: /*.json$
|
|
279
|
+
|
|
280
|
+
# Sitemap location
|
|
281
|
+
# TODO: Update this with your actual sitemap URL after deployment
|
|
282
|
+
Sitemap: https://example.com/sitemap.xml
|
|
283
|
+
`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = {
|
|
287
|
+
createSEOService,
|
|
288
|
+
createStructuredDataUtil,
|
|
289
|
+
createRobotsTxt,
|
|
290
|
+
};
|