skrypt-ai 0.1.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/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/autofix/index.d.ts +46 -0
- package/dist/autofix/index.js +240 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +40 -0
- package/dist/commands/autofix.d.ts +2 -0
- package/dist/commands/autofix.js +143 -0
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.js +320 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +56 -0
- package/dist/commands/review-pr.d.ts +2 -0
- package/dist/commands/review-pr.js +117 -0
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +142 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +2 -0
- package/dist/config/loader.d.ts +9 -0
- package/dist/config/loader.js +82 -0
- package/dist/config/types.d.ts +24 -0
- package/dist/config/types.js +34 -0
- package/dist/generator/generator.d.ts +15 -0
- package/dist/generator/generator.js +144 -0
- package/dist/generator/index.d.ts +4 -0
- package/dist/generator/index.js +4 -0
- package/dist/generator/organizer.d.ts +29 -0
- package/dist/generator/organizer.js +222 -0
- package/dist/generator/types.d.ts +83 -0
- package/dist/generator/types.js +1 -0
- package/dist/generator/writer.d.ts +28 -0
- package/dist/generator/writer.js +320 -0
- package/dist/github/pr-comments.d.ts +40 -0
- package/dist/github/pr-comments.js +308 -0
- package/dist/llm/anthropic-client.d.ts +16 -0
- package/dist/llm/anthropic-client.js +92 -0
- package/dist/llm/index.d.ts +53 -0
- package/dist/llm/index.js +400 -0
- package/dist/llm/llm.manual-test.d.ts +1 -0
- package/dist/llm/llm.manual-test.js +112 -0
- package/dist/llm/llm.mock-test.d.ts +4 -0
- package/dist/llm/llm.mock-test.js +79 -0
- package/dist/llm/openai-client.d.ts +17 -0
- package/dist/llm/openai-client.js +90 -0
- package/dist/llm/types.d.ts +60 -0
- package/dist/llm/types.js +20 -0
- package/dist/scanner/content-type.d.ts +39 -0
- package/dist/scanner/content-type.js +194 -0
- package/dist/scanner/content-type.test.d.ts +1 -0
- package/dist/scanner/content-type.test.js +231 -0
- package/dist/scanner/go.d.ts +20 -0
- package/dist/scanner/go.js +269 -0
- package/dist/scanner/index.d.ts +21 -0
- package/dist/scanner/index.js +137 -0
- package/dist/scanner/python.d.ts +6 -0
- package/dist/scanner/python.js +57 -0
- package/dist/scanner/python_parser.py +230 -0
- package/dist/scanner/rust.d.ts +23 -0
- package/dist/scanner/rust.js +304 -0
- package/dist/scanner/scanner.test.d.ts +1 -0
- package/dist/scanner/scanner.test.js +210 -0
- package/dist/scanner/types.d.ts +50 -0
- package/dist/scanner/types.js +1 -0
- package/dist/scanner/typescript.d.ts +34 -0
- package/dist/scanner/typescript.js +327 -0
- package/dist/scanner/typescript.manual-test.d.ts +1 -0
- package/dist/scanner/typescript.manual-test.js +112 -0
- package/dist/template/docs.json +32 -0
- package/dist/template/mdx-components.tsx +62 -0
- package/dist/template/next-env.d.ts +6 -0
- package/dist/template/next.config.mjs +17 -0
- package/dist/template/package.json +39 -0
- package/dist/template/postcss.config.mjs +5 -0
- package/dist/template/public/search-index.json +1 -0
- package/dist/template/scripts/build-search-index.mjs +120 -0
- package/dist/template/src/app/api/mock/[...path]/route.ts +224 -0
- package/dist/template/src/app/api/openapi/route.ts +48 -0
- package/dist/template/src/app/api/rate-limit/route.ts +84 -0
- package/dist/template/src/app/docs/[...slug]/page.tsx +81 -0
- package/dist/template/src/app/docs/layout.tsx +9 -0
- package/dist/template/src/app/docs/page.mdx +67 -0
- package/dist/template/src/app/error.tsx +63 -0
- package/dist/template/src/app/layout.tsx +71 -0
- package/dist/template/src/app/page.tsx +18 -0
- package/dist/template/src/app/reference/route.ts +36 -0
- package/dist/template/src/app/robots.ts +14 -0
- package/dist/template/src/app/sitemap.ts +64 -0
- package/dist/template/src/components/breadcrumbs.tsx +41 -0
- package/dist/template/src/components/copy-button.tsx +29 -0
- package/dist/template/src/components/docs-layout.tsx +35 -0
- package/dist/template/src/components/edit-link.tsx +39 -0
- package/dist/template/src/components/feedback.tsx +52 -0
- package/dist/template/src/components/header.tsx +66 -0
- package/dist/template/src/components/mdx/accordion.tsx +48 -0
- package/dist/template/src/components/mdx/api-badge.tsx +57 -0
- package/dist/template/src/components/mdx/callout.tsx +111 -0
- package/dist/template/src/components/mdx/card.tsx +62 -0
- package/dist/template/src/components/mdx/changelog.tsx +57 -0
- package/dist/template/src/components/mdx/code-block.tsx +42 -0
- package/dist/template/src/components/mdx/code-group.tsx +125 -0
- package/dist/template/src/components/mdx/code-playground.tsx +322 -0
- package/dist/template/src/components/mdx/go-playground.tsx +235 -0
- package/dist/template/src/components/mdx/heading.tsx +37 -0
- package/dist/template/src/components/mdx/highlighted-code.tsx +89 -0
- package/dist/template/src/components/mdx/index.tsx +15 -0
- package/dist/template/src/components/mdx/param-table.tsx +71 -0
- package/dist/template/src/components/mdx/python-playground.tsx +293 -0
- package/dist/template/src/components/mdx/steps.tsx +43 -0
- package/dist/template/src/components/mdx/tabs.tsx +81 -0
- package/dist/template/src/components/rate-limit-display.tsx +183 -0
- package/dist/template/src/components/search-dialog.tsx +178 -0
- package/dist/template/src/components/sidebar.tsx +129 -0
- package/dist/template/src/components/syntax-theme-selector.tsx +50 -0
- package/dist/template/src/components/table-of-contents.tsx +84 -0
- package/dist/template/src/components/theme-toggle.tsx +46 -0
- package/dist/template/src/components/version-selector.tsx +61 -0
- package/dist/template/src/contexts/syntax-theme.tsx +52 -0
- package/dist/template/src/lib/highlight.ts +83 -0
- package/dist/template/src/lib/search-types.ts +37 -0
- package/dist/template/src/lib/search.ts +125 -0
- package/dist/template/src/lib/utils.ts +6 -0
- package/dist/template/src/styles/globals.css +152 -0
- package/dist/template/tsconfig.json +25 -0
- package/dist/template/tsconfig.tsbuildinfo +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rate Limit Info Endpoint
|
|
5
|
+
* Returns current rate limit status in headers
|
|
6
|
+
*
|
|
7
|
+
* This is a demo endpoint - in production, connect to your actual rate limiter
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Simulated rate limit state (in production, use Redis/database)
|
|
11
|
+
const rateLimitState = new Map<string, { used: number; resetAt: number }>()
|
|
12
|
+
|
|
13
|
+
const LIMIT = 1000
|
|
14
|
+
const WINDOW_MS = 60 * 60 * 1000 // 1 hour
|
|
15
|
+
|
|
16
|
+
function getRateLimitInfo(clientId: string) {
|
|
17
|
+
const now = Date.now()
|
|
18
|
+
let state = rateLimitState.get(clientId)
|
|
19
|
+
|
|
20
|
+
// Reset if window expired
|
|
21
|
+
if (!state || state.resetAt < now) {
|
|
22
|
+
state = {
|
|
23
|
+
used: 0,
|
|
24
|
+
resetAt: now + WINDOW_MS,
|
|
25
|
+
}
|
|
26
|
+
rateLimitState.set(clientId, state)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
limit: LIMIT,
|
|
31
|
+
remaining: Math.max(0, LIMIT - state.used),
|
|
32
|
+
reset: Math.floor(state.resetAt / 1000),
|
|
33
|
+
used: state.used,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function GET(request: Request) {
|
|
38
|
+
// Use IP or auth token as client identifier
|
|
39
|
+
const clientId = request.headers.get('x-forwarded-for') ||
|
|
40
|
+
request.headers.get('authorization') ||
|
|
41
|
+
'anonymous'
|
|
42
|
+
|
|
43
|
+
const info = getRateLimitInfo(clientId)
|
|
44
|
+
|
|
45
|
+
return NextResponse.json(
|
|
46
|
+
{
|
|
47
|
+
message: 'Rate limit info',
|
|
48
|
+
...info,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
headers: {
|
|
52
|
+
'X-RateLimit-Limit': String(info.limit),
|
|
53
|
+
'X-RateLimit-Remaining': String(info.remaining),
|
|
54
|
+
'X-RateLimit-Reset': String(info.reset),
|
|
55
|
+
'X-RateLimit-Used': String(info.used),
|
|
56
|
+
// Standard Rate Limit headers (draft spec)
|
|
57
|
+
'RateLimit-Limit': String(info.limit),
|
|
58
|
+
'RateLimit-Remaining': String(info.remaining),
|
|
59
|
+
'RateLimit-Reset': String(info.reset),
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function HEAD(request: Request) {
|
|
66
|
+
const clientId = request.headers.get('x-forwarded-for') ||
|
|
67
|
+
request.headers.get('authorization') ||
|
|
68
|
+
'anonymous'
|
|
69
|
+
|
|
70
|
+
const info = getRateLimitInfo(clientId)
|
|
71
|
+
|
|
72
|
+
return new NextResponse(null, {
|
|
73
|
+
status: 200,
|
|
74
|
+
headers: {
|
|
75
|
+
'X-RateLimit-Limit': String(info.limit),
|
|
76
|
+
'X-RateLimit-Remaining': String(info.remaining),
|
|
77
|
+
'X-RateLimit-Reset': String(info.reset),
|
|
78
|
+
'X-RateLimit-Used': String(info.used),
|
|
79
|
+
'RateLimit-Limit': String(info.limit),
|
|
80
|
+
'RateLimit-Remaining': String(info.remaining),
|
|
81
|
+
'RateLimit-Reset': String(info.reset),
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { notFound } from 'next/navigation'
|
|
2
|
+
import { readFile, readdir, stat } from 'fs/promises'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import { compileMDX } from 'next-mdx-remote/rsc'
|
|
5
|
+
import * as components from '@/components/mdx'
|
|
6
|
+
|
|
7
|
+
interface PageProps {
|
|
8
|
+
params: Promise<{ slug: string[] }>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function getContent(slug: string[]) {
|
|
12
|
+
const contentDir = join(process.cwd(), 'content', 'docs')
|
|
13
|
+
const filePath = join(contentDir, ...slug)
|
|
14
|
+
|
|
15
|
+
// Try .mdx first, then .md
|
|
16
|
+
for (const ext of ['.mdx', '.md', '/index.mdx', '/index.md']) {
|
|
17
|
+
try {
|
|
18
|
+
const fullPath = filePath + ext
|
|
19
|
+
const content = await readFile(fullPath, 'utf-8')
|
|
20
|
+
return { content, path: fullPath }
|
|
21
|
+
} catch {
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default async function DocPage({ params }: PageProps) {
|
|
30
|
+
const { slug } = await params
|
|
31
|
+
const result = await getContent(slug)
|
|
32
|
+
|
|
33
|
+
if (!result) {
|
|
34
|
+
notFound()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { content } = await compileMDX({
|
|
38
|
+
source: result.content,
|
|
39
|
+
components: components as any,
|
|
40
|
+
options: {
|
|
41
|
+
parseFrontmatter: true,
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="prose max-w-none">
|
|
47
|
+
{content}
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function generateStaticParams() {
|
|
53
|
+
const contentDir = join(process.cwd(), 'content', 'docs')
|
|
54
|
+
const slugs: { slug: string[] }[] = []
|
|
55
|
+
|
|
56
|
+
async function walkDir(dir: string, prefix: string[] = []) {
|
|
57
|
+
try {
|
|
58
|
+
const entries = await readdir(dir, { withFileTypes: true })
|
|
59
|
+
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
await walkDir(join(dir, entry.name), [...prefix, entry.name])
|
|
63
|
+
} else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdx')) {
|
|
64
|
+
const name = entry.name.replace(/\.(mdx?|md)$/, '')
|
|
65
|
+
if (name === 'index') {
|
|
66
|
+
if (prefix.length > 0) {
|
|
67
|
+
slugs.push({ slug: prefix })
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
slugs.push({ slug: [...prefix, name] })
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Directory doesn't exist yet
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await walkDir(contentDir)
|
|
80
|
+
return slugs
|
|
81
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Introduction
|
|
2
|
+
|
|
3
|
+
Welcome to the API documentation. This guide will help you get started with integrating our API into your applications.
|
|
4
|
+
|
|
5
|
+
<CardGroup cols={2}>
|
|
6
|
+
<Card title="Quick Start" icon="Zap" href="/docs/quickstart">
|
|
7
|
+
Get up and running in under 5 minutes
|
|
8
|
+
</Card>
|
|
9
|
+
<Card title="API Reference" icon="Code" href="/docs/api">
|
|
10
|
+
Explore all available endpoints
|
|
11
|
+
</Card>
|
|
12
|
+
</CardGroup>
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
<Info title="Full API Access">
|
|
17
|
+
Our API provides complete access to all platform features with comprehensive documentation.
|
|
18
|
+
</Info>
|
|
19
|
+
|
|
20
|
+
<Steps>
|
|
21
|
+
<Step title="Install the SDK">
|
|
22
|
+
Install our SDK using your preferred package manager.
|
|
23
|
+
</Step>
|
|
24
|
+
<Step title="Configure authentication">
|
|
25
|
+
Set up your API keys and configure authentication.
|
|
26
|
+
</Step>
|
|
27
|
+
<Step title="Make your first request">
|
|
28
|
+
Start making API calls to our endpoints.
|
|
29
|
+
</Step>
|
|
30
|
+
</Steps>
|
|
31
|
+
|
|
32
|
+
## Code Examples
|
|
33
|
+
|
|
34
|
+
<CodeGroup>
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// TypeScript
|
|
38
|
+
import { Client } from 'your-sdk'
|
|
39
|
+
|
|
40
|
+
const client = new Client({
|
|
41
|
+
apiKey: process.env.API_KEY
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const result = await client.getData()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
# Python
|
|
49
|
+
from your_sdk import Client
|
|
50
|
+
|
|
51
|
+
client = Client(api_key=os.environ["API_KEY"])
|
|
52
|
+
|
|
53
|
+
result = client.get_data()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
</CodeGroup>
|
|
57
|
+
|
|
58
|
+
## FAQ
|
|
59
|
+
|
|
60
|
+
<AccordionGroup>
|
|
61
|
+
<Accordion title="How do I get an API key?">
|
|
62
|
+
You can generate an API key from your dashboard. Navigate to Settings → API Keys and click "Create New Key".
|
|
63
|
+
</Accordion>
|
|
64
|
+
<Accordion title="What are the rate limits?">
|
|
65
|
+
Free tier: 100 requests/minute. Pro tier: 1000 requests/minute. Enterprise: Unlimited.
|
|
66
|
+
</Accordion>
|
|
67
|
+
</AccordionGroup>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
|
|
5
|
+
interface ErrorProps {
|
|
6
|
+
error: Error & { digest?: string }
|
|
7
|
+
reset: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function Error({ error, reset }: ErrorProps) {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
console.error('Application error:', error)
|
|
13
|
+
}, [error])
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="min-h-screen flex items-center justify-center p-4">
|
|
17
|
+
<div className="max-w-md w-full text-center">
|
|
18
|
+
<div className="mb-6">
|
|
19
|
+
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-red-100 dark:bg-red-900/30 mb-4">
|
|
20
|
+
<svg
|
|
21
|
+
className="w-8 h-8 text-red-600 dark:text-red-400"
|
|
22
|
+
fill="none"
|
|
23
|
+
viewBox="0 0 24 24"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
>
|
|
26
|
+
<path
|
|
27
|
+
strokeLinecap="round"
|
|
28
|
+
strokeLinejoin="round"
|
|
29
|
+
strokeWidth={2}
|
|
30
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
31
|
+
/>
|
|
32
|
+
</svg>
|
|
33
|
+
</div>
|
|
34
|
+
<h1 className="text-2xl font-bold text-[var(--color-text)] mb-2">
|
|
35
|
+
Something went wrong
|
|
36
|
+
</h1>
|
|
37
|
+
<p className="text-[var(--color-text-secondary)] mb-4">
|
|
38
|
+
An unexpected error occurred. Please try again.
|
|
39
|
+
</p>
|
|
40
|
+
{error.digest && (
|
|
41
|
+
<p className="text-xs text-[var(--color-text-tertiary)] mb-4">
|
|
42
|
+
Error ID: {error.digest}
|
|
43
|
+
</p>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
<div className="flex gap-3 justify-center">
|
|
47
|
+
<button
|
|
48
|
+
onClick={reset}
|
|
49
|
+
className="px-4 py-2 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] transition-colors"
|
|
50
|
+
>
|
|
51
|
+
Try again
|
|
52
|
+
</button>
|
|
53
|
+
<a
|
|
54
|
+
href="/"
|
|
55
|
+
className="px-4 py-2 border border-[var(--color-border)] text-[var(--color-text)] rounded-lg hover:bg-[var(--color-bg-secondary)] transition-colors"
|
|
56
|
+
>
|
|
57
|
+
Go home
|
|
58
|
+
</a>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { Inter } from 'next/font/google'
|
|
3
|
+
import '@/styles/globals.css'
|
|
4
|
+
import { SyntaxThemeProvider } from '@/contexts/syntax-theme'
|
|
5
|
+
|
|
6
|
+
const inter = Inter({ subsets: ['latin'] })
|
|
7
|
+
|
|
8
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://docs.example.com'
|
|
9
|
+
const siteName = process.env.NEXT_PUBLIC_SITE_NAME || 'API Documentation'
|
|
10
|
+
|
|
11
|
+
export const metadata: Metadata = {
|
|
12
|
+
title: {
|
|
13
|
+
default: siteName,
|
|
14
|
+
template: `%s | ${siteName}`,
|
|
15
|
+
},
|
|
16
|
+
description: 'API documentation with interactive examples',
|
|
17
|
+
metadataBase: new URL(siteUrl),
|
|
18
|
+
openGraph: {
|
|
19
|
+
type: 'website',
|
|
20
|
+
locale: 'en_US',
|
|
21
|
+
url: siteUrl,
|
|
22
|
+
siteName,
|
|
23
|
+
title: siteName,
|
|
24
|
+
description: 'API documentation with interactive examples',
|
|
25
|
+
},
|
|
26
|
+
twitter: {
|
|
27
|
+
card: 'summary_large_image',
|
|
28
|
+
title: siteName,
|
|
29
|
+
description: 'API documentation with interactive examples',
|
|
30
|
+
},
|
|
31
|
+
robots: {
|
|
32
|
+
index: true,
|
|
33
|
+
follow: true,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// JSON-LD structured data
|
|
38
|
+
const jsonLd = {
|
|
39
|
+
'@context': 'https://schema.org',
|
|
40
|
+
'@type': 'WebSite',
|
|
41
|
+
name: siteName,
|
|
42
|
+
url: siteUrl,
|
|
43
|
+
description: 'API documentation with interactive examples',
|
|
44
|
+
potentialAction: {
|
|
45
|
+
'@type': 'SearchAction',
|
|
46
|
+
target: `${siteUrl}/docs?q={search_term_string}`,
|
|
47
|
+
'query-input': 'required name=search_term_string',
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default function RootLayout({
|
|
52
|
+
children,
|
|
53
|
+
}: {
|
|
54
|
+
children: React.ReactNode
|
|
55
|
+
}) {
|
|
56
|
+
return (
|
|
57
|
+
<html lang="en" suppressHydrationWarning>
|
|
58
|
+
<head>
|
|
59
|
+
<script
|
|
60
|
+
type="application/ld+json"
|
|
61
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
62
|
+
/>
|
|
63
|
+
</head>
|
|
64
|
+
<body className={inter.className}>
|
|
65
|
+
<SyntaxThemeProvider>
|
|
66
|
+
{children}
|
|
67
|
+
</SyntaxThemeProvider>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Link from 'next/link'
|
|
2
|
+
|
|
3
|
+
export default function Home() {
|
|
4
|
+
return (
|
|
5
|
+
<main className="min-h-screen flex flex-col items-center justify-center p-8">
|
|
6
|
+
<h1 className="text-4xl font-bold mb-4">API Documentation</h1>
|
|
7
|
+
<p className="text-[var(--color-text-secondary)] mb-8 text-center max-w-xl">
|
|
8
|
+
Welcome to the API documentation. Browse the docs to learn how to integrate with our API.
|
|
9
|
+
</p>
|
|
10
|
+
<Link
|
|
11
|
+
href="/docs"
|
|
12
|
+
className="px-6 py-3 bg-[var(--color-primary)] text-white rounded-lg font-medium hover:bg-[var(--color-primary-dark)] transition-colors"
|
|
13
|
+
>
|
|
14
|
+
Get Started
|
|
15
|
+
</Link>
|
|
16
|
+
</main>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ApiReference } from '@scalar/nextjs-api-reference'
|
|
2
|
+
|
|
3
|
+
const MOCK_SERVER_ENABLED = process.env.NEXT_PUBLIC_MOCK_SERVER === 'true'
|
|
4
|
+
|
|
5
|
+
// Build servers list
|
|
6
|
+
const servers = [
|
|
7
|
+
...(MOCK_SERVER_ENABLED ? [{
|
|
8
|
+
url: '/api/mock',
|
|
9
|
+
description: 'Mock Server (returns example responses)',
|
|
10
|
+
}] : []),
|
|
11
|
+
{
|
|
12
|
+
url: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
|
|
13
|
+
description: 'Default',
|
|
14
|
+
},
|
|
15
|
+
...(process.env.NEXT_PUBLIC_API_STAGING_URL ? [{
|
|
16
|
+
url: process.env.NEXT_PUBLIC_API_STAGING_URL,
|
|
17
|
+
description: 'Staging',
|
|
18
|
+
}] : []),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
export const GET = ApiReference({
|
|
22
|
+
spec: {
|
|
23
|
+
url: '/api/openapi',
|
|
24
|
+
},
|
|
25
|
+
// Scalar configuration options
|
|
26
|
+
theme: 'default',
|
|
27
|
+
hideModels: false,
|
|
28
|
+
darkMode: true,
|
|
29
|
+
servers,
|
|
30
|
+
// Enable request sharing (#76)
|
|
31
|
+
showSidebar: true,
|
|
32
|
+
searchHotKey: 'k',
|
|
33
|
+
metaData: {
|
|
34
|
+
title: 'API Reference',
|
|
35
|
+
},
|
|
36
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MetadataRoute } from 'next'
|
|
2
|
+
|
|
3
|
+
export default function robots(): MetadataRoute.Robots {
|
|
4
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://docs.example.com'
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
rules: {
|
|
8
|
+
userAgent: '*',
|
|
9
|
+
allow: '/',
|
|
10
|
+
disallow: ['/api/', '/_next/'],
|
|
11
|
+
},
|
|
12
|
+
sitemap: `${baseUrl}/sitemap.xml`,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { MetadataRoute } from 'next'
|
|
2
|
+
import { readdir } from 'fs/promises'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
|
|
5
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
6
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://docs.example.com'
|
|
7
|
+
|
|
8
|
+
const routes: MetadataRoute.Sitemap = [
|
|
9
|
+
{
|
|
10
|
+
url: baseUrl,
|
|
11
|
+
lastModified: new Date(),
|
|
12
|
+
changeFrequency: 'weekly',
|
|
13
|
+
priority: 1,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
url: `${baseUrl}/docs`,
|
|
17
|
+
lastModified: new Date(),
|
|
18
|
+
changeFrequency: 'weekly',
|
|
19
|
+
priority: 0.9,
|
|
20
|
+
},
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
// Scan content/docs for all MDX files
|
|
24
|
+
try {
|
|
25
|
+
const docsDir = join(process.cwd(), 'content', 'docs')
|
|
26
|
+
const files = await scanDir(docsDir, '')
|
|
27
|
+
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
const slug = file.replace(/\.mdx?$/, '').replace(/\/index$/, '')
|
|
30
|
+
routes.push({
|
|
31
|
+
url: `${baseUrl}/docs/${slug}`,
|
|
32
|
+
lastModified: new Date(),
|
|
33
|
+
changeFrequency: 'weekly',
|
|
34
|
+
priority: 0.8,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// No docs directory yet
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return routes
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function scanDir(dir: string, prefix: string): Promise<string[]> {
|
|
45
|
+
const files: string[] = []
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const entries = await readdir(dir, { withFileTypes: true })
|
|
49
|
+
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
const path = prefix ? `${prefix}/${entry.name}` : entry.name
|
|
52
|
+
|
|
53
|
+
if (entry.isDirectory()) {
|
|
54
|
+
files.push(...await scanDir(join(dir, entry.name), path))
|
|
55
|
+
} else if (entry.name.endsWith('.mdx') || entry.name.endsWith('.md')) {
|
|
56
|
+
files.push(path)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// Directory doesn't exist
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return files
|
|
64
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { usePathname } from 'next/navigation'
|
|
5
|
+
import { ChevronRight, Home } from 'lucide-react'
|
|
6
|
+
|
|
7
|
+
export function Breadcrumbs() {
|
|
8
|
+
const pathname = usePathname()
|
|
9
|
+
const segments = pathname.split('/').filter(Boolean)
|
|
10
|
+
|
|
11
|
+
if (segments.length <= 1) return null
|
|
12
|
+
|
|
13
|
+
const crumbs = segments.map((segment, index) => {
|
|
14
|
+
const href = '/' + segments.slice(0, index + 1).join('/')
|
|
15
|
+
const label = segment
|
|
16
|
+
.replace(/-/g, ' ')
|
|
17
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
18
|
+
|
|
19
|
+
return { href, label }
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<nav className="flex items-center gap-1.5 text-[13px] text-[var(--color-text-tertiary)] mb-4">
|
|
24
|
+
<Link href="/" className="hover:text-[var(--color-text)]">
|
|
25
|
+
<Home size={14} />
|
|
26
|
+
</Link>
|
|
27
|
+
{crumbs.map((crumb, i) => (
|
|
28
|
+
<span key={crumb.href} className="flex items-center gap-1.5">
|
|
29
|
+
<ChevronRight size={14} />
|
|
30
|
+
{i === crumbs.length - 1 ? (
|
|
31
|
+
<span className="text-[var(--color-text)]">{crumb.label}</span>
|
|
32
|
+
) : (
|
|
33
|
+
<Link href={crumb.href} className="hover:text-[var(--color-text)]">
|
|
34
|
+
{crumb.label}
|
|
35
|
+
</Link>
|
|
36
|
+
)}
|
|
37
|
+
</span>
|
|
38
|
+
))}
|
|
39
|
+
</nav>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Copy, Check } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
interface CopyButtonProps {
|
|
7
|
+
text: string
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function CopyButton({ text, className = '' }: CopyButtonProps) {
|
|
12
|
+
const [copied, setCopied] = useState(false)
|
|
13
|
+
|
|
14
|
+
async function handleCopy() {
|
|
15
|
+
await navigator.clipboard.writeText(text)
|
|
16
|
+
setCopied(true)
|
|
17
|
+
setTimeout(() => setCopied(false), 2000)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<button
|
|
22
|
+
onClick={handleCopy}
|
|
23
|
+
className={`p-1.5 rounded text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-tertiary)] transition-colors ${className}`}
|
|
24
|
+
title={copied ? 'Copied!' : 'Copy to clipboard'}
|
|
25
|
+
>
|
|
26
|
+
{copied ? <Check size={14} /> : <Copy size={14} />}
|
|
27
|
+
</button>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Sidebar } from './sidebar'
|
|
5
|
+
import { Header } from './header'
|
|
6
|
+
import { TableOfContents } from './table-of-contents'
|
|
7
|
+
import { Breadcrumbs } from './breadcrumbs'
|
|
8
|
+
|
|
9
|
+
export function DocsLayout({ children }: { children: React.ReactNode }) {
|
|
10
|
+
const [menuOpen, setMenuOpen] = useState(false)
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="min-h-screen">
|
|
14
|
+
<Header
|
|
15
|
+
onMenuToggle={() => setMenuOpen(!menuOpen)}
|
|
16
|
+
menuOpen={menuOpen}
|
|
17
|
+
/>
|
|
18
|
+
<div className="flex">
|
|
19
|
+
<Sidebar
|
|
20
|
+
open={menuOpen}
|
|
21
|
+
onClose={() => setMenuOpen(false)}
|
|
22
|
+
/>
|
|
23
|
+
<main className="flex-1 min-w-0 px-4 md:px-8 py-6 md:ml-[var(--sidebar-width)] xl:mr-[var(--toc-width)]">
|
|
24
|
+
<div className="max-w-3xl mx-auto">
|
|
25
|
+
<Breadcrumbs />
|
|
26
|
+
<article className="prose">
|
|
27
|
+
{children}
|
|
28
|
+
</article>
|
|
29
|
+
</div>
|
|
30
|
+
</main>
|
|
31
|
+
<TableOfContents />
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { usePathname } from 'next/navigation'
|
|
4
|
+
import { Pencil } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
interface EditLinkProps {
|
|
7
|
+
repoUrl?: string
|
|
8
|
+
branch?: string
|
|
9
|
+
docsPath?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function EditLink({
|
|
13
|
+
repoUrl = '',
|
|
14
|
+
branch = 'main',
|
|
15
|
+
docsPath = 'content'
|
|
16
|
+
}: EditLinkProps) {
|
|
17
|
+
const pathname = usePathname()
|
|
18
|
+
|
|
19
|
+
if (!repoUrl) return null
|
|
20
|
+
|
|
21
|
+
// Convert pathname to file path
|
|
22
|
+
const filePath = pathname === '/docs'
|
|
23
|
+
? `${docsPath}/docs/index.mdx`
|
|
24
|
+
: `${docsPath}${pathname}.mdx`
|
|
25
|
+
|
|
26
|
+
const editUrl = `${repoUrl}/edit/${branch}/${filePath}`
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<a
|
|
30
|
+
href={editUrl}
|
|
31
|
+
target="_blank"
|
|
32
|
+
rel="noopener noreferrer"
|
|
33
|
+
className="inline-flex items-center gap-1.5 text-[13px] text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] transition-colors"
|
|
34
|
+
>
|
|
35
|
+
<Pencil size={14} />
|
|
36
|
+
Edit this page
|
|
37
|
+
</a>
|
|
38
|
+
)
|
|
39
|
+
}
|