chore: eslint & ai rules
This commit is contained in:
parent
ebd69c074f
commit
225579ba68
26 changed files with 253 additions and 1548 deletions
1
.clinerules
Symbolic link
1
.clinerules
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
AGENT.md
|
||||
1
.cursor/rules/AGENT.md
Symbolic link
1
.cursor/rules/AGENT.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../AGENT.md
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# API Design Patterns
|
||||
|
||||
## API Route Structure
|
||||
|
||||
### Link Management API
|
||||
Located in the `server/api/link/` directory, containing the following endpoints:
|
||||
|
||||
- [create.post.ts](mdc:server/api/link/create.post.ts): Create new short links
|
||||
- [edit.put.ts](mdc:server/api/link/edit.put.ts): Edit existing links
|
||||
- [delete.post.ts](mdc:server/api/link/delete.post.ts): Delete links
|
||||
- [list.get.ts](mdc:server/api/link/list.get.ts): Get link lists
|
||||
- [query.get.ts](mdc:server/api/link/query.get.ts): Query specific links
|
||||
- [search.get.ts](mdc:server/api/link/search.get.ts): Search links
|
||||
- [upsert.post.ts](mdc:server/api/link/upsert.post.ts): Create or update links
|
||||
- [ai.get.ts](mdc:server/api/link/ai.get.ts): AI-generated slugs
|
||||
|
||||
### Other APIs
|
||||
- [location.ts](mdc:server/api/location.ts): Geographic location information
|
||||
- [verify.ts](mdc:server/api/verify.ts): Authentication
|
||||
- `stats/`: Analytics-related APIs
|
||||
- `logs/`: Logging-related APIs
|
||||
- `ws/`: WebSocket-related APIs
|
||||
|
||||
## API Design Conventions
|
||||
|
||||
### HTTP Methods
|
||||
- `GET`: Retrieve data (queries, lists)
|
||||
- `POST`: Create resources or perform operations
|
||||
- `PUT`: Update resources
|
||||
- `DELETE`: Remove resources
|
||||
|
||||
### Response Format
|
||||
```typescript
|
||||
// Success response
|
||||
{
|
||||
success: true,
|
||||
data: any
|
||||
}
|
||||
|
||||
// Error response
|
||||
{
|
||||
success: false,
|
||||
error: string,
|
||||
code?: number
|
||||
}
|
||||
```
|
||||
|
||||
### Authentication
|
||||
- Use Site Token for authentication
|
||||
- Pass token in request header: `Authorization: Bearer <token>`
|
||||
|
||||
### Data Validation
|
||||
- Use Zod for input validation
|
||||
- Define validation schemas in `schemas/` directory
|
||||
- Unified error handling and response format
|
||||
|
||||
### Cloudflare Integration
|
||||
- Use Cloudflare Workers KV for data storage
|
||||
- Leverage Cloudflare Analytics Engine for statistics
|
||||
- Use Cloudflare AI for slug generation
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Error Codes
|
||||
- `400`: Bad request parameters
|
||||
- `401`: Unauthorized access
|
||||
- `403`: Insufficient permissions
|
||||
- `404`: Resource not found
|
||||
- `429`: Rate limit exceeded
|
||||
- `500`: Internal server error
|
||||
|
||||
### Error Handling Pattern
|
||||
```typescript
|
||||
try {
|
||||
// API logic
|
||||
return { success: true, data: result }
|
||||
} catch (error) {
|
||||
return createError({
|
||||
statusCode: 400,
|
||||
statusMessage: error.message
|
||||
})
|
||||
}
|
||||
```
|
||||
|
|
@ -1,393 +0,0 @@
|
|||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# Coding Standards
|
||||
|
||||
## Code Quality
|
||||
|
||||
### TypeScript Requirements
|
||||
- All new code must use TypeScript
|
||||
- Enable strict type checking
|
||||
- Avoid using `any` type, prefer specific types
|
||||
- Define interfaces or type aliases for complex objects
|
||||
|
||||
```typescript
|
||||
// ✅ Good practice
|
||||
interface LinkData {
|
||||
slug: string
|
||||
url: string
|
||||
createdAt: Date
|
||||
expiresAt?: Date
|
||||
}
|
||||
|
||||
// ❌ Avoid
|
||||
const linkData: any = { ... }
|
||||
```
|
||||
|
||||
### Function and Variable Naming
|
||||
- Use descriptive names
|
||||
- Function names should start with verbs
|
||||
- Boolean variables use `is`, `has`, `should` prefixes
|
||||
- Constants use UPPER_SNAKE_CASE
|
||||
|
||||
```typescript
|
||||
// ✅ Good practice
|
||||
const isValidUrl = (url: string): boolean => { ... }
|
||||
const hasExpired = computed(() => { ... })
|
||||
const MAX_SLUG_LENGTH = 50
|
||||
|
||||
// ❌ Avoid
|
||||
const check = (u: string) => { ... }
|
||||
const expired = computed(() => { ... })
|
||||
const maxLen = 50
|
||||
```
|
||||
|
||||
## Security Requirements
|
||||
|
||||
### Input Validation
|
||||
- All user input must be validated using Zod
|
||||
- API endpoints must validate request parameters
|
||||
- Frontend forms use vee-validate + Zod
|
||||
|
||||
```typescript
|
||||
// API input validation
|
||||
import { z } from 'zod'
|
||||
|
||||
const createLinkSchema = z.object({
|
||||
url: z.string().url(),
|
||||
slug: z.string().min(1).max(50).optional(),
|
||||
expiresAt: z.string().datetime().optional()
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event)
|
||||
const validatedData = createLinkSchema.parse(body)
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
### Authentication
|
||||
- Use Site Token for authentication
|
||||
- Sensitive operations require permission verification
|
||||
- Don't store sensitive information on the client
|
||||
|
||||
### Data Sanitization
|
||||
- Sanitize user-input HTML content
|
||||
- Validate URL format and security
|
||||
- Prevent XSS and injection attacks
|
||||
|
||||
## Performance Best Practices
|
||||
|
||||
### Reactive Data
|
||||
- Use `ref` and `reactive` appropriately
|
||||
- Avoid unnecessary reactive wrapping
|
||||
- Use `readonly` to protect data
|
||||
|
||||
```typescript
|
||||
// ✅ Good practice
|
||||
const { data: links, pending } = await $fetch('/api/links')
|
||||
const filteredLinks = computed(() =>
|
||||
links.value.filter(link => link.isActive)
|
||||
)
|
||||
|
||||
// ❌ Avoid
|
||||
const links = ref([])
|
||||
const filteredLinks = ref([])
|
||||
watch(links, () => {
|
||||
filteredLinks.value = links.value.filter(link => link.isActive)
|
||||
})
|
||||
```
|
||||
|
||||
### Async Operations
|
||||
- Use `async/await` instead of Promise chains
|
||||
- Handle errors and loading states properly
|
||||
- Avoid blocking UI operations
|
||||
|
||||
```typescript
|
||||
// ✅ Good practice
|
||||
const { data, error, pending } = await useFetch('/api/links')
|
||||
|
||||
// Error handling
|
||||
try {
|
||||
const result = await $fetch('/api/link/create', { ... })
|
||||
} catch (error) {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
## Code Organization
|
||||
|
||||
### File Structure
|
||||
- Each file should export only one main functionality
|
||||
- Group related functionality in the same directory
|
||||
- Use `index.ts` as directory entry point
|
||||
|
||||
### Import/Export
|
||||
- Use ES6 module syntax
|
||||
- Organize imports alphabetically
|
||||
- Separate third-party and local imports
|
||||
|
||||
```typescript
|
||||
// Third-party libraries
|
||||
import { ref, computed } from 'vue'
|
||||
import { z } from 'zod'
|
||||
|
||||
// Local imports
|
||||
import type { LinkData } from '~/types'
|
||||
import { validateUrl } from '~/utils/validation'
|
||||
```
|
||||
|
||||
### Comments and Documentation
|
||||
- Add comments for complex logic
|
||||
- Use JSDoc comments for public APIs
|
||||
- Keep comments in sync with code
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Create a new short link
|
||||
* @param url - Original URL
|
||||
* @param slug - Custom slug (optional)
|
||||
* @returns Created link data
|
||||
*/
|
||||
export async function createLink(url: string, slug?: string): Promise<LinkData> {
|
||||
// Implementation logic
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Standards
|
||||
|
||||
### Unit Testing Requirements
|
||||
- **Mandatory for all `server/api` endpoints** - Every API route must have comprehensive unit tests
|
||||
- Use Vitest as the primary testing framework with Nuxt Test Utils
|
||||
- Follow the Test-Driven Development (TDD) approach when possible
|
||||
- Maintain test coverage above 80% for API endpoints
|
||||
|
||||
### Testing Scope and Strategy
|
||||
- **API Layer**: Test all endpoints in [server/api/](mdc:server/api/) directory
|
||||
- **Validation Logic**: Test Zod schemas and input validation
|
||||
- **Error Handling**: Verify error responses and status codes
|
||||
- **Authentication**: Test token validation and authorization flows
|
||||
- **Business Logic**: Cover edge cases and critical functionality
|
||||
|
||||
### Test File Organization
|
||||
```typescript
|
||||
// File naming convention
|
||||
tests/api/link/create.test.ts // Unit tests for API endpoints
|
||||
tests/api/helpers/fixtures.ts // Shared test data
|
||||
tests/sink.spec.ts // Integration/E2E tests
|
||||
```
|
||||
|
||||
### Testing Best Practices
|
||||
- **Write descriptive test names** that explain the behavior being tested
|
||||
- **Test both happy path and error scenarios**
|
||||
- **Use meaningful assertions** that validate expected behavior
|
||||
- **Keep tests independent** - no test should depend on another test's state
|
||||
- **Mock external dependencies** to ensure test reliability
|
||||
|
||||
```typescript
|
||||
// ✅ Good test structure
|
||||
describe('POST /api/link/create', () => {
|
||||
it('should create a new link with valid URL and custom slug', async () => {
|
||||
const response = await $fetch('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: { url: 'https://example.com', slug: 'my-link' }
|
||||
})
|
||||
|
||||
expect(response.success).toBe(true)
|
||||
expect(response.data.slug).toBe('my-link')
|
||||
expect(response.data.url).toBe('https://example.com')
|
||||
})
|
||||
|
||||
it('should reject requests with invalid URL format', async () => {
|
||||
await expect($fetch('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: { url: 'not-a-valid-url' }
|
||||
})).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Test Data Management
|
||||
- Use factory functions for creating test data
|
||||
- Store common test fixtures in `tests/api/helpers/fixtures.ts`
|
||||
- Avoid hardcoded values in tests; use constants and variables
|
||||
|
||||
```typescript
|
||||
// ✅ Good test data management
|
||||
const validLinkPayload = {
|
||||
url: 'https://example.com',
|
||||
slug: 'test-slug',
|
||||
expiresAt: new Date(Date.now() + 86400000).toISOString()
|
||||
}
|
||||
|
||||
const invalidUrlPayload = {
|
||||
url: 'invalid-url-format',
|
||||
slug: 'test-slug'
|
||||
}
|
||||
```
|
||||
|
||||
### Running and Maintaining Tests
|
||||
- Run tests locally before pushing changes: `npm run test`
|
||||
- Use watch mode during development: `npm run test:watch`
|
||||
- Generate coverage reports: `npm run test:coverage`
|
||||
- Tests must pass in CI/CD pipeline before merging
|
||||
|
||||
## Git Commit Conventions
|
||||
|
||||
### Commit Message Format
|
||||
Follow the conventional commit specification:
|
||||
```
|
||||
type(scope): brief description
|
||||
|
||||
Optional body explaining what and why vs how
|
||||
|
||||
Optional footer with issue references
|
||||
```
|
||||
|
||||
### Commit Types
|
||||
- **feat**: New feature implementation
|
||||
- **fix**: Bug fix or issue resolution
|
||||
- **docs**: Documentation updates only
|
||||
- **style**: Code formatting changes (no functional impact)
|
||||
- **refactor**: Code restructuring without changing functionality
|
||||
- **perf**: Performance optimization
|
||||
- **test**: Adding or modifying tests
|
||||
- **chore**: Build process, dependency updates, or auxiliary tool changes
|
||||
- **ci**: Continuous integration configuration changes
|
||||
|
||||
### Scope Examples
|
||||
- **api**: Changes to server/api endpoints
|
||||
- **ui**: Frontend components or styling
|
||||
- **auth**: Authentication related changes
|
||||
- **db**: Database or data storage changes
|
||||
- **config**: Configuration file updates
|
||||
|
||||
### Writing Good Commit Messages
|
||||
|
||||
#### Good Examples
|
||||
```bash
|
||||
feat(api): add AI-powered slug generation endpoint
|
||||
|
||||
Integrate Cloudflare AI Workers to automatically generate
|
||||
meaningful slugs based on URL content and page titles.
|
||||
|
||||
- Add new /api/link/ai endpoint
|
||||
- Implement content analysis logic
|
||||
- Update link creation flow to support AI suggestions
|
||||
- Add comprehensive test coverage
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
```bash
|
||||
fix(auth): resolve token validation edge case
|
||||
|
||||
Handle expired tokens more gracefully by returning proper
|
||||
401 status code instead of throwing unhandled exceptions.
|
||||
|
||||
Fixes #456
|
||||
```
|
||||
|
||||
```bash
|
||||
test(api): add comprehensive coverage for link endpoints
|
||||
|
||||
- Add unit tests for create, edit, delete operations
|
||||
- Include validation error scenarios
|
||||
- Test authentication requirements
|
||||
- Achieve 85% coverage for link management APIs
|
||||
```
|
||||
|
||||
#### Avoid These Patterns
|
||||
```bash
|
||||
# ❌ Too vague
|
||||
fix: stuff
|
||||
|
||||
# ❌ Not descriptive
|
||||
update code
|
||||
|
||||
# ❌ Multiple concerns in one commit
|
||||
feat: add new API endpoint and fix UI bug and update docs
|
||||
|
||||
# ❌ All caps or inconsistent formatting
|
||||
FIX: BROKEN AUTH SYSTEM
|
||||
```
|
||||
|
||||
### Commit Best Practices
|
||||
|
||||
#### Single Responsibility
|
||||
Each commit should represent one logical change:
|
||||
- One feature addition
|
||||
- One bug fix
|
||||
- One refactoring operation
|
||||
- One documentation update
|
||||
|
||||
#### Atomic Commits
|
||||
Commits should be complete and functional:
|
||||
- Code compiles without errors
|
||||
- Tests pass
|
||||
- No broken functionality introduced
|
||||
|
||||
#### Meaningful Descriptions
|
||||
- Use imperative mood ("add feature" not "added feature")
|
||||
- Capitalize the first letter
|
||||
- No period at the end of the subject line
|
||||
- Keep subject line under 50 characters
|
||||
- Use body for detailed explanations when needed
|
||||
|
||||
### Branch Naming Conventions
|
||||
```bash
|
||||
# Feature branches
|
||||
feature/ai-slug-generation
|
||||
feature/link-expiration
|
||||
|
||||
# Bug fix branches
|
||||
fix/auth-token-validation
|
||||
fix/mobile-responsive-layout
|
||||
|
||||
# Documentation branches
|
||||
docs/api-endpoint-guide
|
||||
docs/deployment-instructions
|
||||
|
||||
# Hotfix branches
|
||||
hotfix/security-vulnerability
|
||||
hotfix/production-crash
|
||||
```
|
||||
|
||||
### Pull Request Guidelines
|
||||
|
||||
#### PR Title Format
|
||||
Use the same format as commit messages:
|
||||
```
|
||||
feat(api): add AI-powered slug generation
|
||||
fix(auth): resolve token validation edge case
|
||||
docs(readme): update installation instructions
|
||||
```
|
||||
|
||||
#### PR Description Template
|
||||
```markdown
|
||||
## Summary
|
||||
Brief description of changes made
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Documentation update
|
||||
- [ ] Refactoring
|
||||
- [ ] Performance improvement
|
||||
|
||||
## Testing
|
||||
- [ ] Unit tests added/updated
|
||||
- [ ] Integration tests pass
|
||||
- [ ] Manual testing completed
|
||||
|
||||
## Checklist
|
||||
- [ ] Code follows project style guidelines
|
||||
- [ ] Self-review completed
|
||||
- [ ] Documentation updated if needed
|
||||
- [ ] Tests cover new functionality
|
||||
- [ ] No breaking changes introduced
|
||||
```
|
||||
|
||||
This standardized approach ensures clean, readable commit history and makes it easier to track changes, generate changelogs, and collaborate effectively with team members.
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# Vue Component Design Patterns
|
||||
|
||||
## Component Directory Structure
|
||||
|
||||
### UI Components (`app/components/ui/`)
|
||||
Reusable UI components based on shadcn-vue:
|
||||
- Buttons, inputs, dialogs, and other fundamental components
|
||||
- Follow shadcn-vue design system
|
||||
- Styled with Tailwind CSS
|
||||
- Support theme switching (light/dark mode)
|
||||
|
||||
### Business Components
|
||||
- `dashboard/`: Dashboard-related components
|
||||
- `home/`: Homepage-related components
|
||||
- `login/`: Login-related components
|
||||
- `layouts/`: Layout components
|
||||
- `spark-ui/`: Special UI components
|
||||
|
||||
### Global Components
|
||||
- [SwitchLanguage.vue](mdc:app/components/SwitchLanguage.vue): Language switching component
|
||||
- [SwitchTheme.vue](mdc:app/components/SwitchTheme.vue): Theme switching component
|
||||
|
||||
## Component Development Standards
|
||||
|
||||
### Component Structure
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// Imports
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
// Type definitions
|
||||
interface Props {
|
||||
// props types
|
||||
}
|
||||
|
||||
// Props and Emits
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<{
|
||||
// event types
|
||||
}>()
|
||||
|
||||
// Reactive data
|
||||
const state = ref()
|
||||
|
||||
// Computed properties
|
||||
const computed = computed(() => {
|
||||
// computation logic
|
||||
})
|
||||
|
||||
// Methods
|
||||
function handleAction() {
|
||||
// handler logic
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- template -->
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* styles (if needed) */
|
||||
</style>
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
- Component files use PascalCase: `MyComponent.vue`
|
||||
- Internal variables use camelCase
|
||||
- CSS class names use Tailwind CSS utility classes
|
||||
- Event names use kebab-case
|
||||
|
||||
### Props and Events
|
||||
```typescript
|
||||
// Props type definition
|
||||
interface Props {
|
||||
title: string
|
||||
isVisible?: boolean
|
||||
items: Array<Item>
|
||||
}
|
||||
|
||||
// Events type definition
|
||||
const emit = defineEmits<{
|
||||
update: [value: string]
|
||||
close: []
|
||||
select: [item: Item]
|
||||
}>()
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Composables (`app/composables/`)
|
||||
- Use VueUse utility functions
|
||||
- Create custom composables for shared state management
|
||||
- Follow `use` prefix naming convention
|
||||
|
||||
### Reactive State
|
||||
```typescript
|
||||
// composables/useAuth.ts
|
||||
export function useAuth() {
|
||||
const isAuthenticated = ref(false)
|
||||
const user = ref(null)
|
||||
|
||||
const login = async (token: string) => {
|
||||
// login logic
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
// logout logic
|
||||
}
|
||||
|
||||
return {
|
||||
isAuthenticated: readonly(isAuthenticated),
|
||||
user: readonly(user),
|
||||
login,
|
||||
logout
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Styling and Theming
|
||||
|
||||
### Tailwind CSS
|
||||
- Use Tailwind utility classes for styling
|
||||
- Configuration file: [tailwind.config.js](mdc:tailwind.config.js)
|
||||
- Support responsive design and dark mode
|
||||
|
||||
### Theme System
|
||||
- Use `@nuxtjs/color-mode` module
|
||||
- Support automatic system theme detection
|
||||
- Theme switching component: [SwitchTheme.vue](mdc:app/components/SwitchTheme.vue)
|
||||
|
||||
### Animation Effects
|
||||
- Use `@vueuse/motion` for animations
|
||||
- Use animation classes provided by `tailwindcss-animate`
|
||||
- Keep animations simple and performance-friendly
|
||||
|
||||
## Internationalization
|
||||
|
||||
### Multi-language Support
|
||||
- Use `@nuxtjs/i18n` module
|
||||
- Language switching component: [SwitchLanguage.vue](mdc:app/components/SwitchLanguage.vue)
|
||||
- Store translation files in `i18n/` directory
|
||||
|
||||
### Usage
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<h1>{{ $t('welcome.title') }}</h1>
|
||||
<p>{{ $t('welcome.description') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# Deployment and Infrastructure
|
||||
|
||||
## Cloudflare Platform
|
||||
|
||||
Sink runs entirely on the Cloudflare platform, leveraging the following services:
|
||||
|
||||
### Cloudflare Workers
|
||||
- Serverless computing platform
|
||||
- Global edge network deployment
|
||||
- Configuration file: [wrangler.jsonc](mdc:wrangler.jsonc)
|
||||
|
||||
### Cloudflare Pages
|
||||
- Static website hosting
|
||||
- Automatic build and deployment
|
||||
- Preview deployment support
|
||||
|
||||
### Cloudflare Workers KV
|
||||
- Key-value storage database
|
||||
- Globally distributed storage
|
||||
- Used for storing link data and configuration
|
||||
|
||||
### Cloudflare Analytics Engine
|
||||
- Real-time analytics data collection
|
||||
- High-performance time-series database
|
||||
- Used for link click statistics
|
||||
|
||||
### Cloudflare AI
|
||||
- AI model inference service
|
||||
- Used for automatic slug generation
|
||||
- Model: `@cf/meta/llama-3.1-8b-instruct`
|
||||
|
||||
## Deployment Configuration
|
||||
|
||||
### Environment Variables
|
||||
Defined in `runtimeConfig` in [nuxt.config.ts](mdc:nuxt.config.ts):
|
||||
|
||||
```typescript
|
||||
runtimeConfig: {
|
||||
siteToken: 'SinkCool', // Site access token
|
||||
redirectStatusCode: '301', // Redirect status code
|
||||
linkCacheTtl: 60, // Link cache time
|
||||
redirectWithQuery: false, // Whether to preserve query parameters
|
||||
homeURL: '', // Home URL
|
||||
cfAccountId: '', // Cloudflare Account ID
|
||||
cfApiToken: '', // Cloudflare API token
|
||||
dataset: 'sink', // Analytics dataset name
|
||||
aiModel: '@cf/meta/llama-3.1-8b-instruct', // AI model
|
||||
caseSensitive: false, // Case sensitivity
|
||||
listQueryLimit: 500, // List query limit
|
||||
disableBotAccessLog: false, // Disable bot access logging
|
||||
}
|
||||
```
|
||||
|
||||
### Build Scripts
|
||||
- `pnpm build`: Build for production
|
||||
- `pnpm build:map`: Build map data
|
||||
- `pnpm build:colo`: Build Cloudflare data center information
|
||||
|
||||
### Deployment Commands
|
||||
```bash
|
||||
# Preview deployment
|
||||
pnpm preview
|
||||
|
||||
# Production deployment
|
||||
pnpm deploy
|
||||
|
||||
# Using Wrangler CLI
|
||||
wrangler pages deploy dist
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Caching Strategy
|
||||
- Static assets cached using Cloudflare CDN
|
||||
- Configurable link data cache TTL
|
||||
- Leverage edge computing to reduce latency
|
||||
|
||||
### Route Rules
|
||||
Configured in [nuxt.config.ts](mdc:nuxt.config.ts):
|
||||
|
||||
```typescript
|
||||
routeRules: {
|
||||
'/': {
|
||||
prerender: true, // Prerender homepage
|
||||
},
|
||||
'/dashboard/**': {
|
||||
prerender: true, // Prerender dashboard
|
||||
ssr: false, // Disable server-side rendering
|
||||
},
|
||||
'/dashboard': {
|
||||
redirect: '/dashboard/links', // Redirect to links page
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Build Optimization
|
||||
- Use `NODE_OPTIONS=--max-old-space-size=8192` to increase memory limit
|
||||
- Enable Nuxt's built-in optimization features
|
||||
- Code splitting and lazy loading
|
||||
|
||||
## Monitoring and Logging
|
||||
|
||||
### Analytics Engine
|
||||
- Real-time click data collection
|
||||
- Geographic location statistics
|
||||
- User agent analysis
|
||||
- Referrer statistics
|
||||
|
||||
### Error Handling
|
||||
- Unified error response format
|
||||
- Error logging
|
||||
- Performance monitoring
|
||||
|
||||
### Security
|
||||
- Site Token authentication
|
||||
- CORS configuration
|
||||
- Input validation and sanitization
|
||||
- Malicious link prevention
|
||||
|
||||
## Development Environment
|
||||
|
||||
### Local Development
|
||||
```bash
|
||||
# Start development server
|
||||
pnpm dev
|
||||
|
||||
# Code style checking
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
- Use `.env` file for local environment variables
|
||||
- Use Cloudflare Workers local simulator during development
|
||||
- Support hot reload and live preview
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# Development Guidelines
|
||||
|
||||
## Code Style and Standards
|
||||
|
||||
### TypeScript/JavaScript
|
||||
- Use TypeScript for type-safe development
|
||||
- Follow ESLint rules defined in [eslint.config.mjs](mdc:eslint.config.mjs)
|
||||
- Use `@antfu/eslint-config` as the base configuration
|
||||
- Run `pnpm lint` to check code style, `pnpm lint:fix` to auto-fix issues
|
||||
|
||||
### Vue Components
|
||||
- Use Composition API with `<script setup>` syntax
|
||||
- Name component files using PascalCase
|
||||
- Place UI components in `app/components/ui/` directory
|
||||
- Place business components in `app/components/` directory
|
||||
|
||||
### API Development
|
||||
- Place API routes in `server/api/` directory
|
||||
- Use Nitro's file-system routing
|
||||
- Use consistent error handling format for API responses
|
||||
- Leverage Cloudflare Workers KV for data storage
|
||||
|
||||
## Development Tools and Scripts
|
||||
|
||||
### Common Commands
|
||||
```bash
|
||||
pnpm dev # Start development server
|
||||
pnpm build # Build for production
|
||||
pnpm preview # Preview built application
|
||||
pnpm deploy # Deploy to Cloudflare
|
||||
pnpm lint # Code style checking
|
||||
pnpm lint:fix # Auto-fix code style issues
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
- Use [wrangler.jsonc](mdc:wrangler.jsonc) to configure Cloudflare Workers
|
||||
- Define environment variables in `runtimeConfig` in [nuxt.config.ts](mdc:nuxt.config.ts)
|
||||
- Use `.env` file for development (not committed to version control)
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Performance Optimization
|
||||
- Utilize Nuxt's built-in optimization features
|
||||
- Properly configure `prerender` and SSR settings
|
||||
- Leverage Cloudflare's global CDN network
|
||||
|
||||
### Security
|
||||
- Use Site Token for authentication
|
||||
- Validate inputs using Zod schemas
|
||||
- Follow Cloudflare Workers security best practices
|
||||
|
||||
### Internationalization
|
||||
- Use `@nuxtjs/i18n` module
|
||||
- Store translation files in [i18n/](mdc:i18n/) directory
|
||||
- Support multi-language detection and switching
|
||||
|
||||
### Testing
|
||||
- Use lint-staged to automatically check code style on commit
|
||||
- Follow Git commit conventions
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# Sink Project Overview
|
||||
|
||||
Sink is a simple, speedy, and secure link shortener with analytics, running 100% on Cloudflare.
|
||||
|
||||
## Project Architecture
|
||||
|
||||
This is a full-stack application built with Nuxt.js, utilizing the following tech stack:
|
||||
|
||||
- **Frontend Framework**: Nuxt.js 3 ([nuxt.config.ts](mdc:nuxt.config.ts))
|
||||
- **UI Components**: shadcn-vue + Tailwind CSS
|
||||
- **Database**: Cloudflare Workers KV
|
||||
- **Analytics Engine**: Cloudflare Workers Analytics Engine
|
||||
- **Deployment Platform**: Cloudflare Workers/Pages
|
||||
- **Testing Framework**: Vitest with Nuxt Test Utils
|
||||
- **Type Safety**: TypeScript with strict mode enabled
|
||||
|
||||
## Core Features
|
||||
|
||||
1. **URL Shortening**: Compress long URLs into short links
|
||||
2. **Analytics**: Monitor link click data and gather insightful statistics
|
||||
3. **Custom Slugs**: Support for personalized slugs and case sensitivity
|
||||
4. **AI Slug Generation**: Leverage AI to automatically generate slugs
|
||||
5. **Link Expiration**: Set expiration dates for links
|
||||
|
||||
## Project Structure
|
||||
|
||||
- **app/**: Nuxt application frontend code
|
||||
- `components/`: Vue components
|
||||
- `pages/`: Page routes
|
||||
- `composables/`: Composable functions
|
||||
- `layouts/`: Layout components
|
||||
- `middleware/`: Middleware
|
||||
- **server/**: Backend API code
|
||||
- `api/`: API route handlers ([server/api/](mdc:server/api/))
|
||||
- `middleware/`: Server middleware
|
||||
- `utils/`: Server utility functions
|
||||
- **tests/**: Testing infrastructure
|
||||
- `api/`: Unit tests for API endpoints
|
||||
- `helpers/`: Test utilities and fixtures
|
||||
- [sink.spec.ts](mdc:tests/sink.spec.ts): Integration tests
|
||||
- **i18n/**: Internationalization configuration
|
||||
- **schemas/**: Data validation schemas
|
||||
- **scripts/**: Build scripts
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Testing Philosophy
|
||||
The project follows a **focused testing approach** where unit testing is exclusively applied to the `server/api` directory. This ensures:
|
||||
- **API Reliability**: All backend endpoints are thoroughly tested
|
||||
- **Data Integrity**: Input validation and business logic are verified
|
||||
- **Error Handling**: Proper error responses and status codes
|
||||
- **Security**: Authentication and authorization mechanisms
|
||||
|
||||
### Testing Architecture
|
||||
- **Unit Tests**: Comprehensive coverage of all API endpoints in [server/api/](mdc:server/api/)
|
||||
- **Integration Tests**: E2E testing for critical user flows
|
||||
- **Test Framework**: Vitest with Nuxt Test Utils for API testing
|
||||
- **Coverage Target**: Minimum 80% coverage for API endpoints
|
||||
- **CI/CD Integration**: Automated testing on every pull request
|
||||
|
||||
### Key Test Areas
|
||||
1. **Link Management APIs**: Create, edit, delete, query operations
|
||||
2. **Statistics APIs**: Analytics data retrieval and processing
|
||||
3. **Authentication**: Token validation and security checks
|
||||
4. **Input Validation**: Zod schema validation testing
|
||||
5. **Error Scenarios**: Comprehensive error handling verification
|
||||
|
||||
## Configuration Files
|
||||
|
||||
- [package.json](mdc:package.json): Project dependencies and scripts
|
||||
- [nuxt.config.ts](mdc:nuxt.config.ts): Nuxt configuration
|
||||
- [wrangler.jsonc](mdc:wrangler.jsonc): Cloudflare Workers configuration
|
||||
- [tailwind.config.js](mdc:tailwind.config.js): Tailwind CSS configuration
|
||||
- [eslint.config.mjs](mdc:eslint.config.mjs): ESLint configuration
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Quality Assurance
|
||||
The project maintains high code quality through:
|
||||
- **Type Safety**: Strict TypeScript configuration
|
||||
- **Code Standards**: ESLint and Prettier enforcement
|
||||
- **Testing Requirements**: Mandatory unit tests for all API endpoints
|
||||
- **Pre-commit Hooks**: Automated linting and testing before commits
|
||||
- **Code Reviews**: Comprehensive review process for all changes
|
||||
|
||||
### Development Commands
|
||||
```bash
|
||||
# Development
|
||||
npm run dev # Start development server
|
||||
npm run test # Run all tests
|
||||
|
||||
# Quality Checks
|
||||
npm run lint # Run ESLint
|
||||
npm run lint:fix # Fix ESLint issues
|
||||
npm run build # Build for production
|
||||
|
||||
# Deployment
|
||||
npm run deploy # Deploy to Cloudflare Pages
|
||||
npm run preview # Preview with Wrangler
|
||||
```
|
||||
|
|
@ -1,565 +0,0 @@
|
|||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# Testing Guidelines for Sink
|
||||
|
||||
## Overview
|
||||
|
||||
This project implements comprehensive testing for the backend API layer using Vitest with Cloudflare Workers Pool. Testing focuses exclusively on API endpoint functionality, data validation, authentication, and business logic for the URL shortening service.
|
||||
|
||||
## Testing Stack
|
||||
|
||||
- **Test Framework**: [Vitest](https://vitest.dev/) with [`@cloudflare/vitest-pool-workers`](https://github.com/cloudflare/workers-sdk/tree/main/packages/vitest-pool-workers)
|
||||
- **Mock Generation**: [`@anatine/zod-mock`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock) for generating realistic test data
|
||||
- **Fake Data**: [`@faker-js/faker`](https://fakerjs.dev/) for additional test data generation
|
||||
- **Environment**: Cloudflare Workers runtime with isolated storage
|
||||
|
||||
## Testing Scope
|
||||
|
||||
### What We Test
|
||||
- **API Endpoints**: All routes in [server/api/](mdc:server/api/)
|
||||
- Link management APIs: [create.post.ts](mdc:server/api/link/create.post.ts), [edit.put.ts](mdc:server/api/link/edit.put.ts), [delete.post.ts](mdc:server/api/link/delete.post.ts)
|
||||
- Link retrieval APIs: [list.get.ts](mdc:server/api/link/list.get.ts), [query.get.ts](mdc:server/api/link/query.get.ts), [search.get.ts](mdc:server/api/link/search.get.ts)
|
||||
- Advanced features: [upsert.post.ts](mdc:server/api/link/upsert.post.ts), [ai.get.ts](mdc:server/api/link/ai.get.ts)
|
||||
- Statistics endpoints: [views.get.ts](mdc:server/api/stats/views.get.ts), [counters.get.ts](mdc:server/api/stats/counters.get.ts), [metrics.get.ts](mdc:server/api/stats/metrics.get.ts)
|
||||
- Utility endpoints: [location.ts](mdc:server/api/location.ts), [verify.ts](mdc:server/api/verify.ts)
|
||||
- **Authentication**: Bearer token validation and authorization flows
|
||||
- **Input Validation**: Zod schema validation for all API inputs
|
||||
- **Error Handling**: HTTP status codes and error response structures
|
||||
- **Business Logic**: URL shortening, analytics, and link management features
|
||||
|
||||
### What We Don't Test
|
||||
- Frontend components, pages, or layouts (Vue.js application layer)
|
||||
- Third-party service integrations (external API dependencies)
|
||||
- Cloudflare Workers runtime internals
|
||||
- Database connection layer specifics
|
||||
- Build and deployment processes
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Current Test Organization
|
||||
```
|
||||
tests/
|
||||
├── api/
|
||||
│ ├── link.spec.ts # All link-related endpoint tests
|
||||
│ ├── location.spec.ts # Location API tests
|
||||
│ └── verify.spec.ts # Authentication verification tests
|
||||
├── setup.ts # Global test setup and configuration
|
||||
├── utils.ts # Test utility functions and helpers
|
||||
└── sink.spec.ts # End-to-end integration tests
|
||||
```
|
||||
|
||||
### Recommended Test Structure (for expansion)
|
||||
```
|
||||
tests/
|
||||
├── api/
|
||||
│ ├── link/
|
||||
│ │ ├── create.test.ts # POST /api/link/create
|
||||
│ │ ├── edit.test.ts # PUT /api/link/edit
|
||||
│ │ ├── delete.test.ts # POST /api/link/delete
|
||||
│ │ ├── list.test.ts # GET /api/link/list
|
||||
│ │ ├── query.test.ts # GET /api/link/query
|
||||
│ │ ├── search.test.ts # GET /api/link/search
|
||||
│ │ ├── upsert.test.ts # POST /api/link/upsert
|
||||
│ │ └── ai.test.ts # GET /api/link/ai
|
||||
│ ├── stats/
|
||||
│ │ ├── views.test.ts # GET /api/stats/views
|
||||
│ │ ├── counters.test.ts # GET /api/stats/counters
|
||||
│ │ └── metrics.test.ts # GET /api/stats/metrics
|
||||
│ ├── location.test.ts # GET /api/location
|
||||
│ ├── verify.test.ts # GET /api/verify
|
||||
│ └── helpers/
|
||||
│ ├── fixtures.ts # Test data fixtures
|
||||
│ ├── mocks.ts # Mock implementations
|
||||
│ └── assertions.ts # Custom assertion helpers
|
||||
├── setup.ts
|
||||
├── utils.ts
|
||||
└── integration/
|
||||
└── sink.spec.ts # End-to-end workflow tests
|
||||
```
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### Standard API Test Template
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { generateMock } from '@anatine/zod-mock'
|
||||
import { z } from 'zod'
|
||||
import { fetch, fetchWithAuth } from '../utils'
|
||||
|
||||
// Define schema for test data generation
|
||||
const linkSchema = z.object({
|
||||
url: z.string().url(),
|
||||
slug: z.string().min(1).max(50),
|
||||
})
|
||||
|
||||
describe('/api/link/create', () => {
|
||||
it('creates a new link with valid data', async () => {
|
||||
const payload = generateMock(linkSchema)
|
||||
|
||||
const response = await fetchWithAuth('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
expect(response.status).toBe(201)
|
||||
|
||||
const data = await response.json()
|
||||
expect(data.link).toBeDefined()
|
||||
expect(data.link.url).toBe(payload.url)
|
||||
expect(data.link.slug).toBe(payload.slug)
|
||||
expect(data.shortLink).toContain(payload.slug)
|
||||
})
|
||||
|
||||
it('returns 409 when slug already exists', async () => {
|
||||
const payload = generateMock(linkSchema)
|
||||
|
||||
// Create the link first
|
||||
await fetchWithAuth('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
// Attempt to create duplicate
|
||||
const duplicateResponse = await fetchWithAuth('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
expect(duplicateResponse.status).toBe(409)
|
||||
})
|
||||
|
||||
it('returns 401 without authentication', async () => {
|
||||
const payload = generateMock(linkSchema)
|
||||
|
||||
const response = await fetch('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
|
||||
it('validates required fields', async () => {
|
||||
const invalidPayload = { slug: 'test' } // Missing URL
|
||||
|
||||
const response = await fetchWithAuth('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(invalidPayload),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Authentication Testing Pattern
|
||||
```typescript
|
||||
describe('Authentication', () => {
|
||||
it('accepts valid bearer token', async () => {
|
||||
const response = await fetchWithAuth('/api/link/list')
|
||||
expect(response.status).toBe(200)
|
||||
})
|
||||
|
||||
it('rejects missing authorization header', async () => {
|
||||
const response = await fetch('/api/link/list')
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
|
||||
it('rejects invalid bearer token', async () => {
|
||||
const response = await fetch('/api/link/list', {
|
||||
headers: { Authorization: 'Bearer invalid-token' }
|
||||
})
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
|
||||
it('rejects malformed authorization header', async () => {
|
||||
const response = await fetch('/api/link/list', {
|
||||
headers: { Authorization: 'InvalidFormat' }
|
||||
})
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Input Validation Testing
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
describe('Link validation', () => {
|
||||
const createLinkSchema = z.object({
|
||||
url: z.string().url(),
|
||||
slug: z.string().min(1).max(50).optional(),
|
||||
expiresAt: z.string().datetime().optional(),
|
||||
})
|
||||
|
||||
it('accepts valid URL formats', () => {
|
||||
const testCases = [
|
||||
'https://example.com',
|
||||
'http://sub.domain.com/path',
|
||||
'https://site.com/path?query=value#anchor'
|
||||
]
|
||||
|
||||
testCases.forEach(url => {
|
||||
expect(() => createLinkSchema.parse({ url })).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects invalid URL formats', () => {
|
||||
const testCases = [
|
||||
'not-a-url',
|
||||
'ftp://invalid-protocol.com',
|
||||
'javascript:alert("xss")',
|
||||
''
|
||||
]
|
||||
|
||||
testCases.forEach(url => {
|
||||
expect(() => createLinkSchema.parse({ url })).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
it('validates slug constraints', () => {
|
||||
// Valid slugs
|
||||
expect(() => createLinkSchema.parse({
|
||||
url: 'https://example.com',
|
||||
slug: 'valid-slug'
|
||||
})).not.toThrow()
|
||||
|
||||
// Invalid slugs
|
||||
expect(() => createLinkSchema.parse({
|
||||
url: 'https://example.com',
|
||||
slug: ''
|
||||
})).toThrow()
|
||||
|
||||
expect(() => createLinkSchema.parse({
|
||||
url: 'https://example.com',
|
||||
slug: 'a'.repeat(51)
|
||||
})).toThrow()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Statistics and Analytics Testing
|
||||
```typescript
|
||||
describe('/api/stats/views', () => {
|
||||
it('returns view statistics for valid slug', async () => {
|
||||
// First create a link to get stats for
|
||||
const linkPayload = generateMock(linkSchema)
|
||||
await fetchWithAuth('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(linkPayload),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
const response = await fetchWithAuth(
|
||||
`/api/stats/views?slug=${linkPayload.slug}&timeframe=7d`
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
const data = await response.json()
|
||||
expect(data).toHaveProperty('total')
|
||||
expect(data).toHaveProperty('unique')
|
||||
expect(data).toHaveProperty('timeframe')
|
||||
expect(typeof data.total).toBe('number')
|
||||
expect(typeof data.unique).toBe('number')
|
||||
})
|
||||
|
||||
it('handles non-existent slug gracefully', async () => {
|
||||
const response = await fetchWithAuth('/api/stats/views?slug=non-existent')
|
||||
|
||||
// Should either return 404 or empty stats
|
||||
expect([200, 404]).toContain(response.status)
|
||||
|
||||
if (response.status === 200) {
|
||||
const data = await response.json()
|
||||
expect(data.total).toBe(0)
|
||||
expect(data.unique).toBe(0)
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Test Configuration
|
||||
|
||||
### Vitest Configuration
|
||||
The [vitest.config.ts](mdc:vitest.config.ts) uses Cloudflare Workers pool:
|
||||
|
||||
```typescript
|
||||
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'
|
||||
import { loadEnv } from 'vite'
|
||||
|
||||
export default defineWorkersConfig(({ mode }) => ({
|
||||
test: {
|
||||
env: loadEnv(mode, process.cwd(), ''),
|
||||
globalSetup: './tests/setup.ts',
|
||||
poolOptions: {
|
||||
workers: {
|
||||
singleWorker: true,
|
||||
isolatedStorage: false,
|
||||
wrangler: {
|
||||
configPath: './wrangler.jsonc',
|
||||
},
|
||||
miniflare: {
|
||||
cf: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
### Test Utilities
|
||||
The [tests/utils.ts](mdc:tests/utils.ts) provides essential helpers:
|
||||
|
||||
```typescript
|
||||
import { SELF } from 'cloudflare:test'
|
||||
|
||||
export async function fetchWithAuth(path: string, options?: RequestInit) {
|
||||
return SELF.fetch(`http://localhost${path}`, {
|
||||
...options,
|
||||
headers: {
|
||||
...options?.headers,
|
||||
Authorization: `Bearer ${import.meta.env.NUXT_SITE_TOKEN}`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function fetch(path: string, options?: RequestInit) {
|
||||
return SELF.fetch(`http://localhost${path}`, options)
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Setup
|
||||
Configure test environment variables in [tests/setup.ts](mdc:tests/setup.ts):
|
||||
|
||||
```typescript
|
||||
// Global test setup for environment variables and shared state
|
||||
export default function setup() {
|
||||
// Set up test-specific environment variables
|
||||
process.env.NUXT_SITE_TOKEN = 'test-token-value'
|
||||
// Additional setup as needed
|
||||
}
|
||||
```
|
||||
|
||||
## Test Data Management
|
||||
|
||||
### Using Zod Mock for Realistic Data
|
||||
```typescript
|
||||
import { generateMock } from '@anatine/zod-mock'
|
||||
import { z } from 'zod'
|
||||
|
||||
// Schema-based mock generation
|
||||
const linkSchema = z.object({
|
||||
url: z.string().url(),
|
||||
slug: z.string().min(1).max(50),
|
||||
description: z.string().optional(),
|
||||
})
|
||||
|
||||
const testLink = generateMock(linkSchema)
|
||||
// Generates: { url: "https://example.com", slug: "abc123", description: "..." }
|
||||
```
|
||||
|
||||
### Creating Test Fixtures
|
||||
```typescript
|
||||
// tests/api/helpers/fixtures.ts
|
||||
import { faker } from '@faker-js/faker'
|
||||
|
||||
export const linkFixtures = {
|
||||
valid: {
|
||||
url: 'https://example.com',
|
||||
slug: 'test-link',
|
||||
description: 'A test link for unit testing'
|
||||
},
|
||||
|
||||
withExpiry: {
|
||||
url: 'https://temporary.com',
|
||||
slug: 'temp-link',
|
||||
expiresAt: new Date(Date.now() + 86400000).toISOString() // 24 hours
|
||||
},
|
||||
|
||||
longUrl: {
|
||||
url: 'https://very-long-domain-name.example.com/very/long/path/with/many/segments',
|
||||
slug: 'long-url'
|
||||
}
|
||||
}
|
||||
|
||||
export const statsFixtures = {
|
||||
basicStats: {
|
||||
total: 150,
|
||||
unique: 123,
|
||||
timeframe: '7d'
|
||||
},
|
||||
|
||||
geoStats: [
|
||||
{ country: 'US', count: 45 },
|
||||
{ country: 'UK', count: 30 },
|
||||
{ country: 'DE', count: 25 }
|
||||
]
|
||||
}
|
||||
|
||||
// Dynamic fixture generation
|
||||
export function createRandomLink() {
|
||||
return {
|
||||
url: faker.internet.url(),
|
||||
slug: faker.internet.domainWord(),
|
||||
description: faker.lorem.sentence()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Testing Patterns
|
||||
|
||||
### HTTP Status Code Testing
|
||||
```typescript
|
||||
describe('Error handling', () => {
|
||||
it('returns 400 for malformed JSON', async () => {
|
||||
const response = await fetchWithAuth('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: 'invalid-json{',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
})
|
||||
|
||||
it('returns 422 for validation errors', async () => {
|
||||
const response = await fetchWithAuth('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ url: 'not-a-url' }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
expect(response.status).toBe(422)
|
||||
})
|
||||
|
||||
it('returns 404 for non-existent resources', async () => {
|
||||
const response = await fetchWithAuth('/api/link/query?slug=non-existent')
|
||||
expect(response.status).toBe(404)
|
||||
})
|
||||
|
||||
it('returns 405 for unsupported methods', async () => {
|
||||
const response = await fetchWithAuth('/api/link/create', {
|
||||
method: 'GET', // Should be POST
|
||||
})
|
||||
|
||||
expect(response.status).toBe(405)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Rate Limiting and Performance
|
||||
```typescript
|
||||
describe('Performance and limits', () => {
|
||||
it('handles multiple concurrent requests', async () => {
|
||||
const requests = Array.from({ length: 10 }, (_, i) =>
|
||||
fetchWithAuth('/api/link/create', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
url: `https://example-${i}.com`,
|
||||
slug: `test-${i}`
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
)
|
||||
|
||||
const responses = await Promise.all(requests)
|
||||
|
||||
// All requests should succeed
|
||||
responses.forEach(response => {
|
||||
expect(response.status).toBe(201)
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Test Organization
|
||||
- **Sequential Testing**: Use `describe.sequential()` for tests that modify shared state
|
||||
- **Isolation**: Each test should be independent and not rely on others
|
||||
- **Cleanup**: Clean up created resources when necessary
|
||||
- **Descriptive Names**: Use clear, behavior-focused test descriptions
|
||||
|
||||
### Data Management
|
||||
- **Mock Generation**: Use `@anatine/zod-mock` for schema-consistent test data
|
||||
- **Realistic Data**: Generate realistic URLs, slugs, and user inputs
|
||||
- **Edge Cases**: Test boundary conditions, empty values, and extreme inputs
|
||||
- **State Management**: Be mindful of shared KV storage in sequential tests
|
||||
|
||||
### Performance Considerations
|
||||
- **Focused Tests**: Keep tests fast and focused on specific functionality
|
||||
- **Parallel Execution**: Use parallel execution where state doesn't conflict
|
||||
- **Resource Usage**: Monitor test execution time and resource consumption
|
||||
- **Mock External Services**: Avoid real external API calls in tests
|
||||
|
||||
### Assertion Strategies
|
||||
- **Comprehensive Checks**: Verify response status, structure, and data
|
||||
- **Type Safety**: Ensure response data matches expected TypeScript types
|
||||
- **Error Messages**: Test error response content and structure
|
||||
- **Business Logic**: Verify core URL shortening and analytics logic
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Available Commands
|
||||
```bash
|
||||
# Run all tests
|
||||
npm run test
|
||||
|
||||
# Run tests in watch mode (for development)
|
||||
npm run test -- --watch
|
||||
|
||||
# Run specific test file
|
||||
npm run test tests/api/link.spec.ts
|
||||
|
||||
# Run tests with verbose output
|
||||
npm run test -- --reporter=verbose
|
||||
|
||||
# Run tests matching pattern
|
||||
npm run test -- --grep "authentication"
|
||||
|
||||
# Run tests for specific API group
|
||||
npm run test tests/api/link/ tests/api/stats/
|
||||
```
|
||||
|
||||
### Debugging Tests
|
||||
```bash
|
||||
# Run tests with debugging
|
||||
npm run test -- --inspect-brk
|
||||
|
||||
# Run single test with detailed output
|
||||
npm run test -- --run --reporter=verbose tests/api/link.spec.ts
|
||||
```
|
||||
|
||||
### CI/CD Integration
|
||||
- Tests run automatically on every pull request
|
||||
- Enforce test passage before merging
|
||||
- Consider adding coverage reporting and thresholds
|
||||
- Use test results to gate deployments
|
||||
|
||||
## Coverage Goals
|
||||
|
||||
### Target Areas
|
||||
- **API Endpoints**: 100% coverage of all public endpoints
|
||||
- **Authentication**: Complete coverage of auth flows and edge cases
|
||||
- **Validation**: All input validation paths and error scenarios
|
||||
- **Business Logic**: Core URL shortening and analytics functionality
|
||||
|
||||
### Coverage Exclusions
|
||||
- Configuration files and build scripts
|
||||
- Type definitions and interfaces
|
||||
- Third-party integrations (mock instead)
|
||||
- Development-only utilities
|
||||
|
||||
### Monitoring
|
||||
- Track coverage trends over time
|
||||
- Identify uncovered critical paths
|
||||
- Balance coverage percentage with test quality
|
||||
- Focus on meaningful coverage over arbitrary numbers
|
||||
1
.cursorrules
Symbolic link
1
.cursorrules
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
AGENT.md
|
||||
1
.github/copilot-instructions.md
vendored
Symbolic link
1
.github/copilot-instructions.md
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../AGENT.md
|
||||
1
.kiro/steering/AGENT.md
Symbolic link
1
.kiro/steering/AGENT.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../AGENT.md
|
||||
1
.windsurfrules
Symbolic link
1
.windsurfrules
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
AGENT.md
|
||||
122
AGENT.md
Normal file
122
AGENT.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# AGENT.md
|
||||
|
||||
This file provides guidance for AI assistants working with the Sink project.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Sink is a URL shortening service built with Nuxt 3 and Cloudflare Workers. It provides analytics, real-time tracking, and a modern web interface.
|
||||
|
||||
## Key Technologies
|
||||
|
||||
- **Frontend**: Nuxt 3, Vue 3, TypeScript, Tailwind CSS, shadcn/ui
|
||||
- **Backend**: Cloudflare Workers, H3 (Nitro)
|
||||
- **Database**: Cloudflare KV
|
||||
- **Analytics**: Cloudflare Analytics Engine
|
||||
- **Deployment**: Cloudflare Pages/Workers
|
||||
|
||||
## Project Structure
|
||||
|
||||
```txt
|
||||
Sink/
|
||||
├── app/ # Nuxt 3 application
|
||||
│ ├── components/ # Vue components
|
||||
│ ├── pages/ # File-based routing
|
||||
│ ├── layouts/ # Layout components
|
||||
│ ├── utils/ # Utility functions
|
||||
│ └── middleware/ # Route middleware
|
||||
├── server/ # Server-side API
|
||||
│ ├── api/ # API endpoints
|
||||
│ ├── middleware/ # Server middleware
|
||||
│ └── utils/ # Server utilities
|
||||
├── public/ # Static assets
|
||||
├── schemas/ # TypeScript schemas
|
||||
├── tests/ # Test files
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
## Development Commands
|
||||
|
||||
- `pnpm dev` - Start development server
|
||||
- `pnpm build` - Build for production
|
||||
- `pnpm preview` - Preview production build
|
||||
- `pnpm test` - Run tests
|
||||
- `pnpm lint` - Run ESLint
|
||||
|
||||
## Key Conventions
|
||||
|
||||
### Code Style
|
||||
|
||||
- Use TypeScript for all new code
|
||||
- Follow Vue 3 Composition API patterns
|
||||
- Use PascalCase for component names
|
||||
- Use camelCase for variables and functions
|
||||
- Use kebab-case for CSS classes
|
||||
|
||||
### API Structure
|
||||
|
||||
- RESTful endpoints in `/server/api/`
|
||||
- Use Zod for schema validation in `/schemas/`
|
||||
- Implement proper error handling
|
||||
- Use Cloudflare environment variables for configuration
|
||||
|
||||
### Database
|
||||
|
||||
- Use Cloudflare KV
|
||||
|
||||
### Testing
|
||||
|
||||
- Use Vitest for unit tests
|
||||
- Test files should be co-located with source files
|
||||
- Use the existing test utilities in `/tests/utils.ts`
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Key environment variables to be aware of:
|
||||
|
||||
- `NUXT_CF_ACCOUNT_ID` - Cloudflare account ID
|
||||
- `NUXT_CF_API_TOKEN` - Cloudflare API token
|
||||
- `NUXT_DATASET` - Analytics engine ID
|
||||
- `NUXT_SITE_TOKEN` - Site token for management
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a New API Endpoint
|
||||
|
||||
1. Create file in `/server/api/` with appropriate HTTP method
|
||||
2. Add Zod schema validation in `/schemas/`
|
||||
3. Add tests in `/tests/api/`
|
||||
4. Update documentation if needed
|
||||
|
||||
### Adding a New Component
|
||||
|
||||
1. Use existing UI components from `/app/components/ui/`
|
||||
2. Follow the established component patterns
|
||||
3. Add TypeScript interfaces for props
|
||||
4. Consider adding to storybook if applicable
|
||||
|
||||
## Security Guidelines
|
||||
|
||||
- Never commit secrets or API keys
|
||||
- Use environment variables for sensitive configuration
|
||||
- Validate all user input with Zod schemas
|
||||
- Implement rate limiting on API endpoints
|
||||
- Follow OWASP guidelines for web security
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Use Cloudflare's edge caching where appropriate
|
||||
- Minimize bundle size by using dynamic imports
|
||||
- Optimize images and assets
|
||||
- Use the existing analytics for monitoring
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Check Cloudflare dashboard for worker logs
|
||||
- Use `wrangler tail` for real-time logs
|
||||
- Verify environment variables are properly set
|
||||
|
||||
## Deployment
|
||||
|
||||
- Staging: Automatic on push to `main` branch
|
||||
- Production: Manual trigger via Cloudflare dashboard
|
||||
- Use `wrangler deploy` for manual deployments
|
||||
1
AGENTS.md
Symbolic link
1
AGENTS.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
AGENT.md
|
||||
1
CLAUDE.md
Symbolic link
1
CLAUDE.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
AGENT.md
|
||||
1
GEMINI.md
Symbolic link
1
GEMINI.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
AGENT.md
|
||||
14
README.md
14
README.md
|
|
@ -43,7 +43,7 @@
|
|||
|
||||

|
||||
|
||||
----
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
|
|
@ -82,14 +82,10 @@ Site Token: SinkCool
|
|||
|
||||
We welcome your contributions and PRs.
|
||||
|
||||
- [x] Browser Extension
|
||||
- [Sink Tool](https://github.com/zhuzhuyule/sink-extension)
|
||||
- [x] Raycast Extension
|
||||
- [Raycast-Sink](https://github.com/foru17/raycast-sink)
|
||||
- [x] Apple Shortcuts
|
||||
- [Sink Shortcuts](https://s.search1api.com/sink001)
|
||||
- [x] iOS App
|
||||
- [Sink](https://apps.apple.com/app/id6745417598)
|
||||
- [x] Browser Extension - [Sink Tool](https://github.com/zhuzhuyule/sink-extension)
|
||||
- [x] Raycast Extension - [Raycast-Sink](https://github.com/foru17/raycast-sink)
|
||||
- [x] Apple Shortcuts - [Sink Shortcuts](https://s.search1api.com/sink001)
|
||||
- [x] iOS App - [Sink](https://apps.apple.com/app/id6745417598)
|
||||
- [ ] Enhanced Link Management (with Cloudflare D1)
|
||||
- [ ] Analytics Enhancements (Support for merging filter conditions)
|
||||
- [ ] Dashboard Performance Optimization (Infinite loading)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
--vis-tooltip-padding: none !important;
|
||||
|
||||
--vis-primary-color: 158 64% 52%;
|
||||
--vis-secondary-color: 198 93% 60%;
|
||||
--vis-secondary-color: 198 93% 60%;
|
||||
/* --vis-secondary-color: 160 81% 40%; */
|
||||
/* --vis-secondary-color: var(--primary); */
|
||||
--vis-text-color: var(--muted-foreground);
|
||||
|
|
|
|||
22
docs/api.md
22
docs/api.md
|
|
@ -12,10 +12,10 @@ This place provides an example of creating a short link API. Other APIs are curr
|
|||
POST /api/link/create
|
||||
```
|
||||
|
||||
| Header | Description |
|
||||
| :----- | :------------------------- |
|
||||
| `authorization` | `Bearer SinkCool` |
|
||||
| `content-type` | `application/json` |
|
||||
| Header | Description |
|
||||
| :-------------- | :----------------- |
|
||||
| `authorization` | `Bearer SinkCool` |
|
||||
| `content-type` | `application/json` |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -44,10 +44,10 @@ The BODY data must be JSON.
|
|||
}
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :-------- | :------- | :------------------------- |
|
||||
| `id` | `string` | This is automatically generated by Sink |
|
||||
| `url` | `string` | This is confirmation of the submitted URL and is required. |
|
||||
| `slug` | `string` | This is slug generated by the system, either automatically or from the input (if provided) |
|
||||
| `createdAt` | `timestamp` | This is automatically generated with a UNIX Timestamp. |
|
||||
| `updatedAt` | `timestamp` | This is automatically generated with a UNIX Timestamp. |
|
||||
| Parameter | Type | Description |
|
||||
| :---------- | :---------- | :----------------------------------------------------------------------------------------- |
|
||||
| `id` | `string` | This is automatically generated by Sink |
|
||||
| `url` | `string` | This is confirmation of the submitted URL and is required. |
|
||||
| `slug` | `string` | This is slug generated by the system, either automatically or from the input (if provided) |
|
||||
| `createdAt` | `timestamp` | This is automatically generated with a UNIX Timestamp. |
|
||||
| `updatedAt` | `timestamp` | This is automatically generated with a UNIX Timestamp. |
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ Sink provides some configuration options, which can be referred to in [.env.exam
|
|||
|
||||
## `NUXT_PUBLIC_PREVIEW_MODE`
|
||||
|
||||
> If you are using Worker deployment, this variable needs to be configured in **Settings** -> **Build** -> **Variables and Secrets** and **Settings** -> **Variables and Secrets**.
|
||||
> If you are using Worker deployment, this variable needs to be configured in **Settings** -> **Build** -> **Variables and Secrets** and **Settings** -> **Variables and Secrets**.
|
||||
|
||||
Sets the site to demo mode, the generated links will expire after 5 minutes, and the links cannot be edited or deleted.
|
||||
|
||||
## `NUXT_PUBLIC_SLUG_DEFAULT_LENGTH`
|
||||
|
||||
> If you are using Worker deployment, this variable needs to be configured in **Settings** -> **Build** -> **Variables and Secrets** and **Settings** -> **Variables and Secrets**.
|
||||
> If you are using Worker deployment, this variable needs to be configured in **Settings** -> **Build** -> **Variables and Secrets** and **Settings** -> **Variables and Secrets**.
|
||||
|
||||
Sets the default length of the generated SLUG.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,4 +20,4 @@
|
|||
7. Add Compatibility flags
|
||||
- Go to **Settings** -> **Runtime** -> **Compatibility flags** and set the following flags `nodejs_compat`.
|
||||
8. Redeploy the project.
|
||||
9. To update code, refer to the official GitHub documentation [Syncing a fork branch from the web UI](https://docs.github.com/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork#syncing-a-fork-branch-from-the-web-ui "GitHub: Syncing a fork").
|
||||
9. To update code, refer to the official GitHub documentation [Syncing a fork branch from the web UI](https://docs.github.com/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork#syncing-a-fork-branch-from-the-web-ui 'GitHub: Syncing a fork').
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
3. Update the `kv_namespaces` ID in `wrangler.jsonc` with your own namespace ID.
|
||||
4. Create a project in [Cloudflare Workers](https://developers.cloudflare.com/workers/).
|
||||
5. Select the `Sink` repository and use the following build and deploy commands:
|
||||
- **Build command**: `pnpm run build` or `npm run build`
|
||||
- **Deploy command**: `npx wrangler deploy`
|
||||
- **Build command**: `pnpm run build` or `npm run build`
|
||||
- **Deploy command**: `npx wrangler deploy`
|
||||
|
||||
6. Save and deploy the project.
|
||||
7. After deployment, go to **Settings** -> **Variables and Secrets** -> **Add**, and configure the following environment variables:
|
||||
|
|
@ -16,4 +16,4 @@
|
|||
|
||||
8. Enable Analytics Engine. In **Workers & Pages**, go to **Account details** in the right panel, locate **Analytics Engine**, and click **Set up** to enable the free tier.
|
||||
9. Redeploy the project.
|
||||
10. To update your code, refer to the official GitHub documentation: [Syncing a fork branch from the web UI](https://docs.github.com/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork#syncing-a-fork-branch-from-the-web-ui "GitHub: Syncing a fork").
|
||||
10. To update your code, refer to the official GitHub documentation: [Syncing a fork branch from the web UI](https://docs.github.com/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork#syncing-a-fork-branch-from-the-web-ui 'GitHub: Syncing a fork').
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
// @ts-check
|
||||
import antfu from '@antfu/eslint-config'
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default withNuxt(
|
||||
antfu(),
|
||||
antfu({
|
||||
formatters: true,
|
||||
}),
|
||||
{
|
||||
ignores: ['app/components/ui', '.data', 'public/*.json'],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'no-console': 'off',
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-format": "^1.0.1",
|
||||
"lint-staged": "^16.0.0",
|
||||
"nuxt": "^3.17.4",
|
||||
"shadcn-nuxt": "^2.1.0",
|
||||
|
|
|
|||
102
pnpm-lock.yaml
102
pnpm-lock.yaml
|
|
@ -92,16 +92,16 @@ importers:
|
|||
version: 3.14.0(@faker-js/faker@9.8.0)(zod@3.25.20)
|
||||
'@antfu/eslint-config':
|
||||
specifier: ^4.13.2
|
||||
version: 4.13.2(@vue/compiler-sfc@3.5.14)(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)(vitest@3.1.4(@types/debug@4.1.12)(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))
|
||||
version: 4.13.2(@vue/compiler-sfc@3.5.14)(eslint-plugin-format@1.0.1(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)(vitest@3.1.4(@types/debug@4.1.12)(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))
|
||||
'@cloudflare/vitest-pool-workers':
|
||||
specifier: ^0.8.33
|
||||
version: 0.8.33(@vitest/runner@3.1.4)(@vitest/snapshot@3.1.4)(vitest@3.1.4(@types/debug@4.1.12)(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))
|
||||
'@nuxt/eslint':
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1(@vue/compiler-sfc@3.5.14)(eslint@9.27.0(jiti@2.4.2))(magicast@0.3.5)(typescript@5.7.2)(vite@6.3.5(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))
|
||||
version: 1.4.1(@vue/compiler-sfc@3.5.14)(eslint-plugin-format@1.0.1(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2))(magicast@0.3.5)(typescript@5.7.2)(vite@6.3.5(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))
|
||||
'@nuxt/eslint-config':
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1(@vue/compiler-sfc@3.5.14)(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)
|
||||
version: 1.4.1(@vue/compiler-sfc@3.5.14)(eslint-plugin-format@1.0.1(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)
|
||||
'@nuxthub/core':
|
||||
specifier: ^0.8.27
|
||||
version: 0.8.27(db0@0.3.2)(ioredis@5.6.1)(magicast@0.3.5)(vite@6.3.5(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))
|
||||
|
|
@ -126,6 +126,9 @@ importers:
|
|||
eslint:
|
||||
specifier: ^9.27.0
|
||||
version: 9.27.0(jiti@2.4.2)
|
||||
eslint-plugin-format:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1(eslint@9.27.0(jiti@2.4.2))
|
||||
lint-staged:
|
||||
specifier: ^16.0.0
|
||||
version: 16.0.0
|
||||
|
|
@ -459,6 +462,15 @@ packages:
|
|||
resolution: {integrity: sha512-KrkT6qO5NxqNfy68sBl6CTSoJ4SNDIS5iQArkibhlbGU4LaDukZ3q2HIkh8aUKDio6o4itU4xDR7t82Y2eP1Bg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@dprint/formatter@0.3.0':
|
||||
resolution: {integrity: sha512-N9fxCxbaBOrDkteSOzaCqwWjso5iAe+WJPsHC021JfHNj2ThInPNEF13ORDKta3llq5D1TlclODCvOvipH7bWQ==}
|
||||
|
||||
'@dprint/markdown@0.17.8':
|
||||
resolution: {integrity: sha512-ukHFOg+RpG284aPdIg7iPrCYmMs3Dqy43S1ejybnwlJoFiW02b+6Bbr5cfZKFRYNP3dKGM86BqHEnMzBOyLvvA==}
|
||||
|
||||
'@dprint/toml@0.6.4':
|
||||
resolution: {integrity: sha512-bZXIUjxr0LIuHWshZr/5mtUkOrnh0NKVZEF6ACojW5z7zkJu7s9sV2mMXm8XQDqN4cJzdHYUYzUyEGdfciaLJA==}
|
||||
|
||||
'@emnapi/core@1.4.3':
|
||||
resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==}
|
||||
|
||||
|
|
@ -1481,6 +1493,10 @@ packages:
|
|||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@pkgr/core@0.1.2':
|
||||
resolution: {integrity: sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@polka/url@1.0.0-next.25':
|
||||
resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==}
|
||||
|
||||
|
|
@ -3518,6 +3534,11 @@ packages:
|
|||
eslint-flat-config-utils@2.1.0:
|
||||
resolution: {integrity: sha512-6fjOJ9tS0k28ketkUcQ+kKptB4dBZY2VijMZ9rGn8Cwnn1SH0cZBoPXT8AHBFHxmHcLFQK9zbELDinZ2Mr1rng==}
|
||||
|
||||
eslint-formatting-reporter@0.0.0:
|
||||
resolution: {integrity: sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==}
|
||||
peerDependencies:
|
||||
eslint: '>=8.40.0'
|
||||
|
||||
eslint-import-resolver-node@0.3.9:
|
||||
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
|
||||
|
||||
|
|
@ -3537,6 +3558,9 @@ packages:
|
|||
peerDependencies:
|
||||
eslint: '*'
|
||||
|
||||
eslint-parser-plain@0.1.1:
|
||||
resolution: {integrity: sha512-KRgd6wuxH4U8kczqPp+Oyk4irThIhHWxgFgLDtpgjUGVIS3wGrJntvZW/p6hHq1T4FOwnOtCNkvAI4Kr+mQ/Hw==}
|
||||
|
||||
eslint-plugin-antfu@3.1.1:
|
||||
resolution: {integrity: sha512-7Q+NhwLfHJFvopI2HBZbSxWXngTwBLKxW1AGXLr2lEGxcEIK/AsDs8pn8fvIizl5aZjBbVbVK5ujmMpBe4Tvdg==}
|
||||
peerDependencies:
|
||||
|
|
@ -3553,6 +3577,11 @@ packages:
|
|||
peerDependencies:
|
||||
eslint: '>=8'
|
||||
|
||||
eslint-plugin-format@1.0.1:
|
||||
resolution: {integrity: sha512-Tdns+CDjS+m7QrM85wwRi2yLae88XiWVdIOXjp9mDII0pmTBQlczPCmjpKnjiUIY3yPZNLqb5Ms/A/JXcBF2Dw==}
|
||||
peerDependencies:
|
||||
eslint: ^8.40.0 || ^9.0.0
|
||||
|
||||
eslint-plugin-import-x@4.12.2:
|
||||
resolution: {integrity: sha512-0jVUgJQipbs0yUfLe7LwYD6p8rIGqCysWZdyJFgkPzDyJgiKpuCaXlywKUAWgJ6u1nLpfrdt21B60OUkupyBrQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
|
@ -3748,6 +3777,9 @@ packages:
|
|||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
fast-diff@1.3.0:
|
||||
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
|
||||
|
||||
fast-fifo@1.3.2:
|
||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||
|
||||
|
|
@ -5541,6 +5573,15 @@ packages:
|
|||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
prettier-linter-helpers@1.0.0:
|
||||
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
prettier@3.6.2:
|
||||
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
pretty-bytes@6.1.1:
|
||||
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
|
||||
engines: {node: ^14.13.1 || >=16.0.0}
|
||||
|
|
@ -6082,6 +6123,10 @@ packages:
|
|||
resolution: {integrity: sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
||||
synckit@0.9.3:
|
||||
resolution: {integrity: sha512-JJoOEKTfL1urb1mDoEblhD9NhEbWmq9jHEMEnxoC4ujUaZ4itA8vKgwkFAyNClgxplLi9tsUKX+EduK0p/l7sg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
system-architecture@0.1.0:
|
||||
resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -6938,7 +6983,7 @@ snapshots:
|
|||
randexp: 0.5.3
|
||||
zod: 3.25.20
|
||||
|
||||
'@antfu/eslint-config@4.13.2(@vue/compiler-sfc@3.5.14)(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)(vitest@3.1.4(@types/debug@4.1.12)(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))':
|
||||
'@antfu/eslint-config@4.13.2(@vue/compiler-sfc@3.5.14)(eslint-plugin-format@1.0.1(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)(vitest@3.1.4(@types/debug@4.1.12)(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 1.1.0
|
||||
'@clack/prompts': 0.10.1
|
||||
|
|
@ -6977,6 +7022,8 @@ snapshots:
|
|||
toml-eslint-parser: 0.10.0
|
||||
vue-eslint-parser: 10.1.3(eslint@9.27.0(jiti@2.4.2))
|
||||
yaml-eslint-parser: 1.3.0
|
||||
optionalDependencies:
|
||||
eslint-plugin-format: 1.0.1(eslint@9.27.0(jiti@2.4.2))
|
||||
transitivePeerDependencies:
|
||||
- '@eslint/json'
|
||||
- '@vue/compiler-sfc'
|
||||
|
|
@ -7265,6 +7312,12 @@ snapshots:
|
|||
gonzales-pe: 4.3.0
|
||||
node-source-walk: 6.0.2
|
||||
|
||||
'@dprint/formatter@0.3.0': {}
|
||||
|
||||
'@dprint/markdown@0.17.8': {}
|
||||
|
||||
'@dprint/toml@0.6.4': {}
|
||||
|
||||
'@emnapi/core@1.4.3':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.0.2
|
||||
|
|
@ -8070,7 +8123,7 @@ snapshots:
|
|||
- utf-8-validate
|
||||
- vue
|
||||
|
||||
'@nuxt/eslint-config@1.4.1(@vue/compiler-sfc@3.5.14)(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)':
|
||||
'@nuxt/eslint-config@1.4.1(@vue/compiler-sfc@3.5.14)(eslint-plugin-format@1.0.1(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)':
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 1.1.0
|
||||
'@clack/prompts': 0.10.1
|
||||
|
|
@ -8093,6 +8146,8 @@ snapshots:
|
|||
local-pkg: 1.1.1
|
||||
pathe: 2.0.3
|
||||
vue-eslint-parser: 10.1.3(eslint@9.27.0(jiti@2.4.2))
|
||||
optionalDependencies:
|
||||
eslint-plugin-format: 1.0.1(eslint@9.27.0(jiti@2.4.2))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/compiler-sfc'
|
||||
- supports-color
|
||||
|
|
@ -8107,11 +8162,11 @@ snapshots:
|
|||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@nuxt/eslint@1.4.1(@vue/compiler-sfc@3.5.14)(eslint@9.27.0(jiti@2.4.2))(magicast@0.3.5)(typescript@5.7.2)(vite@6.3.5(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))':
|
||||
'@nuxt/eslint@1.4.1(@vue/compiler-sfc@3.5.14)(eslint-plugin-format@1.0.1(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2))(magicast@0.3.5)(typescript@5.7.2)(vite@6.3.5(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@eslint/config-inspector': 1.0.2(eslint@9.27.0(jiti@2.4.2))
|
||||
'@nuxt/devtools-kit': 2.4.1(magicast@0.3.5)(vite@6.3.5(@types/node@20.12.11)(jiti@2.4.2)(terser@5.31.0)(yaml@2.8.0))
|
||||
'@nuxt/eslint-config': 1.4.1(@vue/compiler-sfc@3.5.14)(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)
|
||||
'@nuxt/eslint-config': 1.4.1(@vue/compiler-sfc@3.5.14)(eslint-plugin-format@1.0.1(eslint@9.27.0(jiti@2.4.2)))(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)
|
||||
'@nuxt/eslint-plugin': 1.4.1(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)
|
||||
'@nuxt/kit': 3.17.4(magicast@0.3.5)
|
||||
chokidar: 4.0.3
|
||||
|
|
@ -8513,6 +8568,8 @@ snapshots:
|
|||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@pkgr/core@0.1.2': {}
|
||||
|
||||
'@polka/url@1.0.0-next.25': {}
|
||||
|
||||
'@poppinss/colors@4.1.4':
|
||||
|
|
@ -10659,6 +10716,11 @@ snapshots:
|
|||
dependencies:
|
||||
pathe: 2.0.3
|
||||
|
||||
eslint-formatting-reporter@0.0.0(eslint@9.27.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
eslint: 9.27.0(jiti@2.4.2)
|
||||
prettier-linter-helpers: 1.0.0
|
||||
|
||||
eslint-import-resolver-node@0.3.9:
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
|
|
@ -10677,6 +10739,8 @@ snapshots:
|
|||
dependencies:
|
||||
eslint: 9.27.0(jiti@2.4.2)
|
||||
|
||||
eslint-parser-plain@0.1.1: {}
|
||||
|
||||
eslint-plugin-antfu@3.1.1(eslint@9.27.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
eslint: 9.27.0(jiti@2.4.2)
|
||||
|
|
@ -10693,6 +10757,17 @@ snapshots:
|
|||
eslint: 9.27.0(jiti@2.4.2)
|
||||
eslint-compat-utils: 0.5.1(eslint@9.27.0(jiti@2.4.2))
|
||||
|
||||
eslint-plugin-format@1.0.1(eslint@9.27.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
'@dprint/formatter': 0.3.0
|
||||
'@dprint/markdown': 0.17.8
|
||||
'@dprint/toml': 0.6.4
|
||||
eslint: 9.27.0(jiti@2.4.2)
|
||||
eslint-formatting-reporter: 0.0.0(eslint@9.27.0(jiti@2.4.2))
|
||||
eslint-parser-plain: 0.1.1
|
||||
prettier: 3.6.2
|
||||
synckit: 0.9.3
|
||||
|
||||
eslint-plugin-import-x@4.12.2(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2):
|
||||
dependencies:
|
||||
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0(jiti@2.4.2))(typescript@5.7.2)
|
||||
|
|
@ -10998,6 +11073,8 @@ snapshots:
|
|||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-diff@1.3.0: {}
|
||||
|
||||
fast-fifo@1.3.2: {}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
|
|
@ -13195,6 +13272,12 @@ snapshots:
|
|||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
prettier-linter-helpers@1.0.0:
|
||||
dependencies:
|
||||
fast-diff: 1.3.0
|
||||
|
||||
prettier@3.6.2: {}
|
||||
|
||||
pretty-bytes@6.1.1: {}
|
||||
|
||||
printable-characters@1.0.42: {}
|
||||
|
|
@ -13809,6 +13892,11 @@ snapshots:
|
|||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
synckit@0.9.3:
|
||||
dependencies:
|
||||
'@pkgr/core': 0.1.2
|
||||
tslib: 2.8.1
|
||||
|
||||
system-architecture@0.1.0: {}
|
||||
|
||||
tailwind-config-viewer@2.0.4(tailwindcss@3.4.17):
|
||||
|
|
|
|||
Loading…
Reference in a new issue