Initial commit
This commit is contained in:
425
commands/new-collection.md
Normal file
425
commands/new-collection.md
Normal file
@@ -0,0 +1,425 @@
|
||||
# Create New Astro Content Collection
|
||||
|
||||
This command guides you through creating a new content collection in an Astro project with proper schema definition, directory structure, and TypeScript integration.
|
||||
|
||||
## Instructions
|
||||
|
||||
Follow these steps to create a new content collection:
|
||||
|
||||
### 1. Gather Requirements
|
||||
|
||||
Ask the user for:
|
||||
- Collection name (e.g., 'blog', 'docs', 'products', 'team')
|
||||
- Collection purpose (what type of content?)
|
||||
- Required fields and their types
|
||||
- Optional fields
|
||||
- File format preference (markdown, JSON, YAML, etc.)
|
||||
|
||||
### 2. Design Schema Fields
|
||||
|
||||
Based on the collection purpose, determine appropriate fields:
|
||||
|
||||
**For blog/articles:**
|
||||
- title (string, required)
|
||||
- description (string, required)
|
||||
- pubDate (date, required)
|
||||
- author (string, required)
|
||||
- tags (array of strings)
|
||||
- draft (boolean, default false)
|
||||
- image (object with url and alt)
|
||||
|
||||
**For documentation:**
|
||||
- title (string, required)
|
||||
- description (string, required)
|
||||
- sidebar (object with order, label)
|
||||
- lastUpdated (date, optional)
|
||||
- contributors (array of strings)
|
||||
|
||||
**For products:**
|
||||
- name (string, required)
|
||||
- description (string, required)
|
||||
- price (number, required)
|
||||
- category (enum)
|
||||
- features (array of strings)
|
||||
- inStock (boolean)
|
||||
- images (array of objects)
|
||||
|
||||
**For team members:**
|
||||
- name (string, required)
|
||||
- role (string, required)
|
||||
- bio (string, required)
|
||||
- avatar (string, required)
|
||||
- social (object with URLs)
|
||||
- startDate (date, required)
|
||||
|
||||
### 3. Locate or Create Config File
|
||||
|
||||
1. Check if config exists:
|
||||
- Look for `src/content/config.ts`
|
||||
- Or `src/content.config.ts`
|
||||
|
||||
2. If it doesn't exist, create `src/content/config.ts`
|
||||
|
||||
3. Ensure imports are present:
|
||||
```typescript
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
import { glob } from 'astro/loaders';
|
||||
```
|
||||
|
||||
### 4. Define Collection Schema
|
||||
|
||||
Create the collection definition with appropriate Zod schema:
|
||||
|
||||
```typescript
|
||||
const [collectionName] = defineCollection({
|
||||
loader: glob({ pattern: "**/*.md", base: "./src/content/[collectionName]" }),
|
||||
schema: z.object({
|
||||
// Define fields based on requirements
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
Use appropriate Zod types:
|
||||
- `z.string()` - Text fields
|
||||
- `z.number()` - Numeric fields
|
||||
- `z.boolean()` - True/false fields
|
||||
- `z.coerce.date()` - Date fields (with auto-conversion)
|
||||
- `z.array(z.string())` - Arrays
|
||||
- `z.object({...})` - Nested objects
|
||||
- `z.enum(['value1', 'value2'])` - Fixed options
|
||||
- Add `.optional()` for optional fields
|
||||
- Add `.default(value)` for default values
|
||||
|
||||
### 5. Add to Collections Export
|
||||
|
||||
Update the export statement:
|
||||
|
||||
```typescript
|
||||
export const collections = {
|
||||
// existing collections...
|
||||
[collectionName]: [collectionName]
|
||||
};
|
||||
```
|
||||
|
||||
### 6. Create Collection Directory
|
||||
|
||||
Create the directory structure:
|
||||
```bash
|
||||
mkdir -p src/content/[collectionName]
|
||||
```
|
||||
|
||||
### 7. Create Sample Content File
|
||||
|
||||
Create a sample file to test the schema:
|
||||
|
||||
**For markdown:**
|
||||
`src/content/[collectionName]/sample.md`
|
||||
|
||||
**For JSON:**
|
||||
`src/content/[collectionName]/sample.json`
|
||||
|
||||
**For YAML:**
|
||||
`src/content/[collectionName]/sample.yml`
|
||||
|
||||
Include frontmatter/data that matches the schema.
|
||||
|
||||
### 8. Validate Schema
|
||||
|
||||
1. Start the dev server: `npm run dev`
|
||||
2. Check for TypeScript errors
|
||||
3. Verify the sample file validates correctly
|
||||
4. Check `.astro/types.d.ts` for generated types
|
||||
|
||||
### 9. Document Usage
|
||||
|
||||
Create a comment block in the config explaining the collection:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* [Collection Name] Collection
|
||||
*
|
||||
* Purpose: [Description]
|
||||
*
|
||||
* Required fields:
|
||||
* - field1: description
|
||||
* - field2: description
|
||||
*
|
||||
* Optional fields:
|
||||
* - field3: description
|
||||
*/
|
||||
const [collectionName] = defineCollection({...});
|
||||
```
|
||||
|
||||
### 10. Provide Query Examples
|
||||
|
||||
Show the user how to query the new collection:
|
||||
|
||||
```typescript
|
||||
import { getCollection, getEntry } from 'astro:content';
|
||||
|
||||
// Get all items
|
||||
const items = await getCollection('[collectionName]');
|
||||
|
||||
// Get published items (if applicable)
|
||||
const published = await getCollection('[collectionName]', ({ data }) => {
|
||||
return !data.draft;
|
||||
});
|
||||
|
||||
// Get single item
|
||||
const item = await getEntry('[collectionName]', 'slug');
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
**Collection Config** (`src/content/config.ts`):
|
||||
|
||||
```typescript
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
import { glob } from 'astro/loaders';
|
||||
|
||||
/**
|
||||
* Blog Posts Collection
|
||||
*
|
||||
* Stores blog articles and tutorials
|
||||
*/
|
||||
const blog = defineCollection({
|
||||
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
|
||||
schema: z.object({
|
||||
title: z.string().max(80),
|
||||
description: z.string().max(160),
|
||||
pubDate: z.coerce.date(),
|
||||
updatedDate: z.coerce.date().optional(),
|
||||
author: z.string(),
|
||||
tags: z.array(z.string()).default([]),
|
||||
draft: z.boolean().default(false),
|
||||
featured: z.boolean().default(false),
|
||||
image: z.object({
|
||||
url: z.string(),
|
||||
alt: z.string()
|
||||
}).optional()
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* Documentation Collection
|
||||
*
|
||||
* Technical documentation and guides
|
||||
*/
|
||||
const docs = defineCollection({
|
||||
loader: glob({ pattern: "**/*.mdx", base: "./src/content/docs" }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
sidebar: z.object({
|
||||
order: z.number(),
|
||||
label: z.string().optional(),
|
||||
hidden: z.boolean().default(false)
|
||||
}).optional(),
|
||||
lastUpdated: z.coerce.date().optional(),
|
||||
contributors: z.array(z.string()).default([])
|
||||
})
|
||||
});
|
||||
|
||||
export const collections = { blog, docs };
|
||||
```
|
||||
|
||||
**Sample Blog Post** (`src/content/blog/first-post.md`):
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: 'My First Post'
|
||||
description: 'This is my first blog post'
|
||||
pubDate: 2024-01-15
|
||||
author: 'John Doe'
|
||||
tags: ['intro', 'blogging']
|
||||
draft: false
|
||||
featured: true
|
||||
---
|
||||
|
||||
# Hello World
|
||||
|
||||
This is my first post!
|
||||
```
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] Collection requirements gathered from user
|
||||
- [ ] Schema fields identified and typed
|
||||
- [ ] Config file located or created
|
||||
- [ ] Collection definition added with proper schema
|
||||
- [ ] Loader configured for correct file pattern
|
||||
- [ ] Zod types match requirements
|
||||
- [ ] Collection exported in collections object
|
||||
- [ ] Collection directory created
|
||||
- [ ] Sample content file created
|
||||
- [ ] Sample file validates against schema
|
||||
- [ ] Dev server starts without errors
|
||||
- [ ] TypeScript types generated correctly
|
||||
- [ ] Documentation comments added
|
||||
- [ ] Query examples provided to user
|
||||
|
||||
## Schema Best Practices
|
||||
|
||||
### String Validation
|
||||
```typescript
|
||||
title: z.string().min(1).max(100)
|
||||
email: z.string().email()
|
||||
url: z.string().url()
|
||||
slug: z.string().regex(/^[a-z0-9-]+$/)
|
||||
```
|
||||
|
||||
### Number Validation
|
||||
```typescript
|
||||
price: z.number().positive()
|
||||
age: z.number().int().min(0).max(120)
|
||||
rating: z.number().min(1).max(5)
|
||||
```
|
||||
|
||||
### Dates
|
||||
```typescript
|
||||
pubDate: z.coerce.date() // Converts strings to Date
|
||||
deadline: z.date() // Requires Date object
|
||||
```
|
||||
|
||||
### Arrays
|
||||
```typescript
|
||||
tags: z.array(z.string()).min(1) // At least one
|
||||
images: z.array(z.string()).max(10) // Maximum 10
|
||||
authors: z.array(z.string()).default([])
|
||||
```
|
||||
|
||||
### Enums
|
||||
```typescript
|
||||
status: z.enum(['draft', 'published', 'archived'])
|
||||
priority: z.enum(['low', 'medium', 'high'])
|
||||
```
|
||||
|
||||
### Optional Fields
|
||||
```typescript
|
||||
subtitle: z.string().optional()
|
||||
updatedDate: z.coerce.date().optional()
|
||||
```
|
||||
|
||||
### Default Values
|
||||
```typescript
|
||||
draft: z.boolean().default(false)
|
||||
views: z.number().default(0)
|
||||
tags: z.array(z.string()).default([])
|
||||
```
|
||||
|
||||
### Nested Objects
|
||||
```typescript
|
||||
author: z.object({
|
||||
name: z.string(),
|
||||
email: z.string().email(),
|
||||
url: z.string().url().optional()
|
||||
})
|
||||
```
|
||||
|
||||
### Records (Dynamic Keys)
|
||||
```typescript
|
||||
metadata: z.record(z.string()) // Any string keys
|
||||
settings: z.record(z.boolean()) // Boolean values
|
||||
```
|
||||
|
||||
## Loader Patterns
|
||||
|
||||
### Markdown Files
|
||||
```typescript
|
||||
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" })
|
||||
```
|
||||
|
||||
### MDX Files
|
||||
```typescript
|
||||
loader: glob({ pattern: "**/*.mdx", base: "./src/content/docs" })
|
||||
```
|
||||
|
||||
### Multiple Formats
|
||||
```typescript
|
||||
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/mixed" })
|
||||
```
|
||||
|
||||
### JSON Files
|
||||
```typescript
|
||||
loader: glob({ pattern: "**/*.json", base: "./src/content/data" })
|
||||
```
|
||||
|
||||
### YAML Files
|
||||
```typescript
|
||||
loader: glob({ pattern: "**/*.{yml,yaml}", base: "./src/content/config" })
|
||||
```
|
||||
|
||||
### Single File
|
||||
```typescript
|
||||
loader: file("src/data/settings.json")
|
||||
```
|
||||
|
||||
## Common Collection Types
|
||||
|
||||
### Blog
|
||||
```typescript
|
||||
const blog = defineCollection({
|
||||
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
pubDate: z.coerce.date(),
|
||||
author: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
draft: z.boolean().default(false)
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### Portfolio Projects
|
||||
```typescript
|
||||
const projects = defineCollection({
|
||||
loader: glob({ pattern: "**/*.md", base: "./src/content/projects" }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
technologies: z.array(z.string()),
|
||||
liveUrl: z.string().url().optional(),
|
||||
githubUrl: z.string().url().optional(),
|
||||
thumbnail: z.string(),
|
||||
date: z.coerce.date()
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### Product Catalog
|
||||
```typescript
|
||||
const products = defineCollection({
|
||||
loader: glob({ pattern: "**/*.json", base: "./src/content/products" }),
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
price: z.number().positive(),
|
||||
category: z.enum(['software', 'hardware', 'service']),
|
||||
features: z.array(z.string()),
|
||||
inStock: z.boolean()
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## Error Prevention
|
||||
|
||||
- Always use `z.coerce.date()` for date strings
|
||||
- Provide defaults for booleans and arrays
|
||||
- Use enums for fixed value sets
|
||||
- Validate URLs and emails with built-in validators
|
||||
- Test schema with sample content first
|
||||
- Restart dev server after schema changes
|
||||
- Export all collections in the collections object
|
||||
- Use consistent naming (kebab-case for directories)
|
||||
|
||||
## Tips
|
||||
|
||||
- Start with required fields, add optional later
|
||||
- Use TypeScript comments for documentation
|
||||
- Create sample content immediately
|
||||
- Test validation with invalid data
|
||||
- Consider future needs when designing schema
|
||||
- Use subdirectories for organization
|
||||
- Keep schemas focused and specific
|
||||
- Reuse common patterns across collections
|
||||
Reference in New Issue
Block a user