@raftlabs/raftstack 1.0.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.
- package/.claude/skills/backend/SKILL.md +802 -0
- package/.claude/skills/code-quality/SKILL.md +318 -0
- package/.claude/skills/database/SKILL.md +465 -0
- package/.claude/skills/react/SKILL.md +418 -0
- package/.claude/skills/seo/SKILL.md +446 -0
- package/LICENSE +21 -0
- package/README.md +291 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2009 -0
- package/dist/cli.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: seo
|
|
3
|
+
description: Use when creating web pages, adding metadata, optimizing Core Web Vitals, implementing structured data, or when pages aren't ranking or showing rich snippets in Google
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SEO Optimization
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
SEO requires three pillars: technical performance (Core Web Vitals), proper metadata, and structured data for rich snippets. All three matter for ranking.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Creating any public-facing page
|
|
15
|
+
- Adding metadata to Next.js pages
|
|
16
|
+
- Implementing structured data (JSON-LD)
|
|
17
|
+
- Optimizing page load performance
|
|
18
|
+
- Debugging missing rich snippets in Google
|
|
19
|
+
|
|
20
|
+
## The Iron Rules
|
|
21
|
+
|
|
22
|
+
### 1. Core Web Vitals Are Ranking Factors
|
|
23
|
+
|
|
24
|
+
| Metric | Target | What It Measures | Key Optimization |
|
|
25
|
+
|--------|--------|------------------|------------------|
|
|
26
|
+
| **LCP** (Largest Contentful Paint) | < 2.5s | Main content load time | `priority` on hero images |
|
|
27
|
+
| **INP** (Interaction to Next Paint) | < 200ms | Responsiveness | scheduler.yield() for long tasks |
|
|
28
|
+
| **CLS** (Cumulative Layout Shift) | < 0.1 | Visual stability | Always set width/height |
|
|
29
|
+
|
|
30
|
+
#### LCP Optimization with Next.js Image
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// ❌ BAD: Unoptimized image kills LCP
|
|
34
|
+
<img src="/hero.jpg" alt="Hero" />
|
|
35
|
+
|
|
36
|
+
// ✅ GOOD: Next.js Image with priority for LCP
|
|
37
|
+
import Image from 'next/image';
|
|
38
|
+
|
|
39
|
+
<Image
|
|
40
|
+
src="/hero.jpg"
|
|
41
|
+
alt="Hero"
|
|
42
|
+
width={1200}
|
|
43
|
+
height={630}
|
|
44
|
+
priority // Preloads, disables lazy loading
|
|
45
|
+
placeholder="blur" // Shows blur during load
|
|
46
|
+
blurDataURL="data:image/..." // Optional blur data
|
|
47
|
+
sizes="(max-width: 768px) 100vw, 50vw"
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
// ✅ GOOD: Static import auto-generates blur
|
|
51
|
+
import heroImage from './hero.jpg';
|
|
52
|
+
|
|
53
|
+
<Image
|
|
54
|
+
src={heroImage}
|
|
55
|
+
alt="Hero"
|
|
56
|
+
priority
|
|
57
|
+
placeholder="blur" // Blur data automatically provided
|
|
58
|
+
/>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### INP Optimization (< 200ms)
|
|
62
|
+
|
|
63
|
+
INP has three phases to optimize:
|
|
64
|
+
|
|
65
|
+
**1. Input Delay** - Time until event handler starts
|
|
66
|
+
**2. Processing Time** - Event handler execution
|
|
67
|
+
**3. Presentation Delay** - Time to next frame
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// ❌ BAD: Long synchronous task blocks interactions
|
|
71
|
+
button.addEventListener('click', () => {
|
|
72
|
+
// Heavy computation blocks UI for 500ms
|
|
73
|
+
const result = expensiveCalculation();
|
|
74
|
+
updateUI(result);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ✅ GOOD: Break up with scheduler.yield()
|
|
78
|
+
button.addEventListener('click', async () => {
|
|
79
|
+
const data = await fetchData();
|
|
80
|
+
|
|
81
|
+
// Yield to allow interactions to process
|
|
82
|
+
await scheduler.yield();
|
|
83
|
+
|
|
84
|
+
processData(data);
|
|
85
|
+
|
|
86
|
+
await scheduler.yield();
|
|
87
|
+
|
|
88
|
+
updateUI();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ✅ GOOD: Debounce rapid interactions
|
|
92
|
+
const debouncedSearch = debounce((query) => {
|
|
93
|
+
performSearch(query);
|
|
94
|
+
}, 300);
|
|
95
|
+
|
|
96
|
+
input.addEventListener('input', (e) => {
|
|
97
|
+
debouncedSearch(e.target.value);
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Key INP strategies:**
|
|
102
|
+
- Use `scheduler.yield()` in long tasks (> 50ms)
|
|
103
|
+
- Debounce rapid user inputs
|
|
104
|
+
- Lazy load below-fold interactivity
|
|
105
|
+
- Avoid large DOM updates on interaction
|
|
106
|
+
|
|
107
|
+
#### CLS Optimization
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// ❌ BAD: CLS - no dimensions on dynamic content
|
|
111
|
+
<div className="product-list">
|
|
112
|
+
{products.map(p => <ProductCard key={p.id} product={p} />)}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
// ✅ GOOD: Reserve space with aspect-ratio or fixed height
|
|
116
|
+
<div className="product-list min-h-[400px]">
|
|
117
|
+
{products.map(p => <ProductCard key={p.id} product={p} />)}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
// ✅ GOOD: Always set dimensions on images
|
|
121
|
+
<Image
|
|
122
|
+
src="/product.jpg"
|
|
123
|
+
alt="Product"
|
|
124
|
+
width={400} // Prevents CLS
|
|
125
|
+
height={300}
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 2. Use Next.js Metadata API Correctly
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// app/products/[slug]/page.tsx
|
|
133
|
+
import { Metadata } from 'next';
|
|
134
|
+
|
|
135
|
+
export async function generateMetadata({ params }): Promise<Metadata> {
|
|
136
|
+
const product = await getProduct(params.slug);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
title: `${product.name} | Brand - $${product.price}`,
|
|
140
|
+
description: truncate(product.description, 155), // Max 155 chars
|
|
141
|
+
|
|
142
|
+
alternates: {
|
|
143
|
+
canonical: `https://site.com/products/${product.slug}`,
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
// OpenGraph - use 'product' for e-commerce
|
|
147
|
+
openGraph: {
|
|
148
|
+
type: 'product', // NOT 'website' for products
|
|
149
|
+
title: product.name,
|
|
150
|
+
description: truncate(product.description, 155),
|
|
151
|
+
images: [{
|
|
152
|
+
url: product.image,
|
|
153
|
+
width: 1200,
|
|
154
|
+
height: 630,
|
|
155
|
+
alt: product.name,
|
|
156
|
+
}],
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
twitter: {
|
|
160
|
+
card: 'summary_large_image',
|
|
161
|
+
title: product.name,
|
|
162
|
+
description: truncate(product.description, 155),
|
|
163
|
+
images: [product.image],
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
robots: {
|
|
167
|
+
index: true,
|
|
168
|
+
follow: true,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 3. Product Structured Data for Rich Snippets
|
|
175
|
+
|
|
176
|
+
Google shows price, availability, and reviews in search results. You MUST include:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Minimum required fields for Product rich snippet
|
|
180
|
+
const productJsonLd = {
|
|
181
|
+
'@context': 'https://schema.org',
|
|
182
|
+
'@type': 'Product',
|
|
183
|
+
name: product.name,
|
|
184
|
+
image: product.images,
|
|
185
|
+
description: product.description,
|
|
186
|
+
sku: product.sku,
|
|
187
|
+
brand: {
|
|
188
|
+
'@type': 'Brand',
|
|
189
|
+
name: product.brand,
|
|
190
|
+
},
|
|
191
|
+
offers: {
|
|
192
|
+
'@type': 'Offer',
|
|
193
|
+
url: `https://site.com/products/${product.slug}`,
|
|
194
|
+
price: product.price,
|
|
195
|
+
priceCurrency: 'USD',
|
|
196
|
+
availability: 'https://schema.org/InStock', // or OutOfStock
|
|
197
|
+
priceValidUntil: '2025-12-31', // Required for validity
|
|
198
|
+
},
|
|
199
|
+
// Optional but highly recommended:
|
|
200
|
+
aggregateRating: product.rating ? {
|
|
201
|
+
'@type': 'AggregateRating',
|
|
202
|
+
ratingValue: product.rating.value,
|
|
203
|
+
reviewCount: product.rating.count,
|
|
204
|
+
} : undefined,
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 4. JSON-LD Rendering Pattern
|
|
209
|
+
|
|
210
|
+
Use the Next.js recommended pattern for safe JSON-LD rendering:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// NOTE: This pattern is safe because JSON.stringify escapes content
|
|
214
|
+
// and the replace() prevents script tag injection
|
|
215
|
+
function JsonLd({ data }: { data: object }) {
|
|
216
|
+
const safeJson = JSON.stringify(data).replace(/</g, '\\u003c');
|
|
217
|
+
return (
|
|
218
|
+
<script
|
|
219
|
+
type="application/ld+json"
|
|
220
|
+
// eslint-disable-next-line react/no-danger
|
|
221
|
+
dangerouslySetInnerHTML={{ __html: safeJson }}
|
|
222
|
+
/>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 5. Essential Structured Data Types
|
|
228
|
+
|
|
229
|
+
| Page Type | Required Schema | Rich Result |
|
|
230
|
+
|-----------|-----------------|-------------|
|
|
231
|
+
| Product | Product + Offer | Price, availability in search |
|
|
232
|
+
| Article | Article + Author | Article snippet |
|
|
233
|
+
| FAQ | FAQPage | Expandable Q&A in search |
|
|
234
|
+
| Recipe | Recipe | Recipe card with image |
|
|
235
|
+
| Local Business | LocalBusiness | Knowledge panel |
|
|
236
|
+
| Breadcrumbs | BreadcrumbList | Breadcrumb trail in results |
|
|
237
|
+
|
|
238
|
+
#### Article Schema
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const articleJsonLd = {
|
|
242
|
+
'@context': 'https://schema.org',
|
|
243
|
+
'@type': 'Article',
|
|
244
|
+
headline: post.title,
|
|
245
|
+
image: post.coverImage,
|
|
246
|
+
datePublished: post.publishedAt,
|
|
247
|
+
dateModified: post.updatedAt,
|
|
248
|
+
author: {
|
|
249
|
+
'@type': 'Person',
|
|
250
|
+
name: post.author.name,
|
|
251
|
+
url: `https://site.com/authors/${post.author.slug}`,
|
|
252
|
+
},
|
|
253
|
+
publisher: {
|
|
254
|
+
'@type': 'Organization',
|
|
255
|
+
name: 'Brand Name',
|
|
256
|
+
logo: {
|
|
257
|
+
'@type': 'ImageObject',
|
|
258
|
+
url: 'https://site.com/logo.png',
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### FAQPage Schema
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const faqJsonLd = {
|
|
268
|
+
'@context': 'https://schema.org',
|
|
269
|
+
'@type': 'FAQPage',
|
|
270
|
+
mainEntity: faqs.map((faq) => ({
|
|
271
|
+
'@type': 'Question',
|
|
272
|
+
name: faq.question,
|
|
273
|
+
acceptedAnswer: {
|
|
274
|
+
'@type': 'Answer',
|
|
275
|
+
text: faq.answer,
|
|
276
|
+
},
|
|
277
|
+
})),
|
|
278
|
+
};
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Sitemap & Robots
|
|
282
|
+
|
|
283
|
+
### Dynamic Sitemap Generation
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
// app/sitemap.ts
|
|
287
|
+
import { MetadataRoute } from 'next';
|
|
288
|
+
|
|
289
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
290
|
+
const products = await db.product.findMany({
|
|
291
|
+
select: { slug: true, updatedAt: true },
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const productUrls = products.map((product) => ({
|
|
295
|
+
url: `https://site.com/products/${product.slug}`,
|
|
296
|
+
lastModified: product.updatedAt,
|
|
297
|
+
changeFrequency: 'weekly' as const,
|
|
298
|
+
priority: 0.8,
|
|
299
|
+
}));
|
|
300
|
+
|
|
301
|
+
return [
|
|
302
|
+
{
|
|
303
|
+
url: 'https://site.com',
|
|
304
|
+
lastModified: new Date(),
|
|
305
|
+
changeFrequency: 'daily',
|
|
306
|
+
priority: 1,
|
|
307
|
+
},
|
|
308
|
+
...productUrls,
|
|
309
|
+
];
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Robots.txt
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// app/robots.ts
|
|
317
|
+
import { MetadataRoute } from 'next';
|
|
318
|
+
|
|
319
|
+
export default function robots(): MetadataRoute.Robots {
|
|
320
|
+
return {
|
|
321
|
+
rules: [
|
|
322
|
+
{
|
|
323
|
+
userAgent: '*',
|
|
324
|
+
allow: '/',
|
|
325
|
+
disallow: ['/admin/', '/api/'],
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
userAgent: 'Googlebot',
|
|
329
|
+
allow: '/',
|
|
330
|
+
crawlDelay: 0,
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
sitemap: 'https://site.com/sitemap.xml',
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Quick Reference: Metadata Checklist
|
|
339
|
+
|
|
340
|
+
Every page needs:
|
|
341
|
+
- [ ] `title` - Unique, < 60 chars, includes primary keyword
|
|
342
|
+
- [ ] `description` - Compelling, < 155 chars, includes CTA
|
|
343
|
+
- [ ] `canonical` URL - Prevents duplicate content
|
|
344
|
+
- [ ] `openGraph` - For social sharing (1200x630 images)
|
|
345
|
+
- [ ] `twitter` card - For Twitter/X sharing
|
|
346
|
+
- [ ] `robots` - index/noindex directive
|
|
347
|
+
|
|
348
|
+
For e-commerce:
|
|
349
|
+
- [ ] OpenGraph `type: 'product'` (not 'website')
|
|
350
|
+
- [ ] Product JSON-LD with offers
|
|
351
|
+
- [ ] Breadcrumb JSON-LD
|
|
352
|
+
|
|
353
|
+
## Testing & Measurement
|
|
354
|
+
|
|
355
|
+
### Validation Tools (Before Deploy)
|
|
356
|
+
|
|
357
|
+
Test structured data BEFORE deploying:
|
|
358
|
+
- [Rich Results Test](https://search.google.com/test/rich-results) - Google's official tool
|
|
359
|
+
- [Schema Markup Validator](https://validator.schema.org/) - schema.org validator
|
|
360
|
+
- Chrome DevTools → Lighthouse → SEO audit
|
|
361
|
+
|
|
362
|
+
### Performance Measurement
|
|
363
|
+
|
|
364
|
+
| Tool | What It Measures | Use For |
|
|
365
|
+
|------|------------------|---------|
|
|
366
|
+
| **PageSpeed Insights** | Field data (28 days) | Official Core Web Vitals scores |
|
|
367
|
+
| **Chrome User Experience Report (CrUX)** | Real user data | P75 scores for ranking |
|
|
368
|
+
| **Lighthouse (DevTools)** | Lab data (simulated) | Local testing, not for ranking |
|
|
369
|
+
| **Search Console** | Core Web Vitals report | Per-URL performance in field |
|
|
370
|
+
|
|
371
|
+
**Critical:** Only **Field Data** (real users) affects Google rankings. Lab data helps debug but doesn't count for SEO.
|
|
372
|
+
|
|
373
|
+
### Core Web Vitals Testing Strategy
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// Measure INP in production
|
|
377
|
+
new PerformanceObserver((list) => {
|
|
378
|
+
for (const entry of list.getEntries()) {
|
|
379
|
+
if (entry.entryType === 'event') {
|
|
380
|
+
const inp = entry.processingStart - entry.startTime;
|
|
381
|
+
if (inp > 200) {
|
|
382
|
+
console.warn('Slow INP:', {
|
|
383
|
+
duration: inp,
|
|
384
|
+
name: entry.name,
|
|
385
|
+
target: entry.target,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}).observe({ type: 'event', buffered: true });
|
|
391
|
+
|
|
392
|
+
// Log slow LCP
|
|
393
|
+
new PerformanceObserver((list) => {
|
|
394
|
+
const entries = list.getEntries();
|
|
395
|
+
const lcp = entries[entries.length - 1];
|
|
396
|
+
if (lcp.renderTime > 2500) {
|
|
397
|
+
console.warn('Slow LCP:', {
|
|
398
|
+
duration: lcp.renderTime,
|
|
399
|
+
element: lcp.element,
|
|
400
|
+
url: lcp.url,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}).observe({ type: 'largest-contentful-paint', buffered: true });
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## References
|
|
407
|
+
|
|
408
|
+
- [Core Web Vitals INP Guide](https://web.dev/articles/inp) - Official INP optimization patterns
|
|
409
|
+
- [Optimize INP](https://web.dev/articles/optimize-inp) - Three-phase optimization approach
|
|
410
|
+
- [Next.js Image Component](https://nextjs.org/docs/app/api-reference/components/image) - priority, placeholder, sizes
|
|
411
|
+
- [Next.js Metadata API](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) - generateMetadata patterns
|
|
412
|
+
- [Schema.org](https://schema.org/) - Structured data vocabulary
|
|
413
|
+
|
|
414
|
+
**Version Notes:**
|
|
415
|
+
- INP replaced FID as Core Web Vital (March 2024)
|
|
416
|
+
- Next.js 15: Enhanced Image component with automatic blur
|
|
417
|
+
- Good INP: < 200ms (improving from 500ms → 200ms = 22% engagement boost)
|
|
418
|
+
|
|
419
|
+
## Red Flags - STOP and Fix
|
|
420
|
+
|
|
421
|
+
| Thought | Reality |
|
|
422
|
+
|---------|---------|
|
|
423
|
+
| "SEO is just meta tags" | Core Web Vitals are ranking factors. Optimize performance. |
|
|
424
|
+
| "I'll add structured data later" | No rich snippets = lower CTR. Add from day one. |
|
|
425
|
+
| "LCP doesn't matter for this page" | Every page's performance affects site-wide ranking. |
|
|
426
|
+
| "Using img tag is fine" | Next.js Image handles optimization. Always use it. |
|
|
427
|
+
| "OpenGraph type='website' is fine" | Use 'product' for products, 'article' for articles. |
|
|
428
|
+
| "Lab data (Lighthouse) is good enough" | Only field data counts for ranking. Test with real users. |
|
|
429
|
+
| "INP is too complex to optimize" | Use scheduler.yield() and debouncing. Start simple. |
|
|
430
|
+
| "I don't need a sitemap for small sites" | Sitemaps help discovery. Generate dynamically. |
|
|
431
|
+
|
|
432
|
+
## Common Mistakes
|
|
433
|
+
|
|
434
|
+
| Mistake | Fix |
|
|
435
|
+
|---------|-----|
|
|
436
|
+
| HTML img instead of Next.js Image | Use `next/image` with priority for LCP |
|
|
437
|
+
| Missing `width`/`height` on images | Always specify to prevent CLS |
|
|
438
|
+
| Description > 160 chars | Truncate to 155 with ellipsis |
|
|
439
|
+
| No canonical URL | Add `alternates.canonical` |
|
|
440
|
+
| Missing `priceValidUntil` in Offer | Required for Product rich snippets |
|
|
441
|
+
| OpenGraph type='website' for products | Use type='product' |
|
|
442
|
+
| No structured data validation | Test with Rich Results Test before deploy |
|
|
443
|
+
| Long tasks without scheduler.yield() | Break up tasks > 50ms to improve INP |
|
|
444
|
+
| Testing only with Lighthouse | Use PageSpeed Insights for field data |
|
|
445
|
+
| No placeholder on LCP images | Add `placeholder="blur"` for perceived performance |
|
|
446
|
+
| Dynamic sitemap with hardcoded URLs | Fetch from database for automatic updates |
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Raftlabs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|