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,112 @@
|
|
|
1
|
+
import { scanFile, scanDirectory } from './index.js';
|
|
2
|
+
async function runTests() {
|
|
3
|
+
console.log('=== TypeScript Scanner Tests ===\n');
|
|
4
|
+
// Test 1: Scan single TypeScript file
|
|
5
|
+
console.log('Test 1: Scan single TypeScript file');
|
|
6
|
+
const result = await scanFile('testdata/sample.ts');
|
|
7
|
+
if (result.errors.length > 0) {
|
|
8
|
+
console.log(' ✗ errors:', result.errors);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
console.log(` found ${result.elements.length} elements`);
|
|
12
|
+
console.log(` language: ${result.language}`);
|
|
13
|
+
// Check we found expected elements
|
|
14
|
+
const names = result.elements.map(e => e.name);
|
|
15
|
+
const expected = ['greet', 'fetchData', 'processItems', 'Calculator', 'constructor', 'add', 'multiply'];
|
|
16
|
+
for (const name of expected) {
|
|
17
|
+
if (!names.includes(name)) {
|
|
18
|
+
console.log(` ✗ missing expected element: ${name}`);
|
|
19
|
+
console.log(' found:', names);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Check private/non-exported elements are excluded
|
|
24
|
+
const excluded = ['_privateMethod', '_privateFunction', 'internalHelper'];
|
|
25
|
+
for (const name of excluded) {
|
|
26
|
+
if (names.includes(name)) {
|
|
27
|
+
console.log(` ✗ should have excluded: ${name}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
console.log(' ✓ found all expected elements');
|
|
32
|
+
console.log(' ✓ excluded private/non-exported elements\n');
|
|
33
|
+
// Test 2: Check function details
|
|
34
|
+
console.log('Test 2: Check function details');
|
|
35
|
+
const greet = result.elements.find(e => e.name === 'greet');
|
|
36
|
+
if (greet.kind !== 'function') {
|
|
37
|
+
console.log(` ✗ greet.kind should be 'function', got '${greet.kind}'`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
if (greet.parameters.length !== 2) {
|
|
41
|
+
console.log(` ✗ greet should have 2 params, got ${greet.parameters.length}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
if (greet.returnType !== 'string') {
|
|
45
|
+
console.log(` ✗ greet.returnType should be 'string', got '${greet.returnType}'`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
console.log(' ✓ function details correct\n');
|
|
49
|
+
// Test 3: Check async function
|
|
50
|
+
console.log('Test 3: Check async function');
|
|
51
|
+
const fetchData = result.elements.find(e => e.name === 'fetchData');
|
|
52
|
+
if (!fetchData.isAsync) {
|
|
53
|
+
console.log(' ✗ fetchData should be async');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.log(' ✓ async detected correctly\n');
|
|
57
|
+
// Test 4: Check arrow function
|
|
58
|
+
console.log('Test 4: Check arrow function');
|
|
59
|
+
const processItems = result.elements.find(e => e.name === 'processItems');
|
|
60
|
+
if (processItems.kind !== 'function') {
|
|
61
|
+
console.log(` ✗ processItems.kind should be 'function', got '${processItems.kind}'`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
if (!processItems.isAsync) {
|
|
65
|
+
console.log(' ✗ processItems should be async');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
console.log(' ✓ arrow function detected correctly\n');
|
|
69
|
+
// Test 5: Check class and methods
|
|
70
|
+
console.log('Test 5: Check class and methods');
|
|
71
|
+
const calculator = result.elements.find(e => e.name === 'Calculator');
|
|
72
|
+
const addMethod = result.elements.find(e => e.name === 'add');
|
|
73
|
+
if (calculator.kind !== 'class') {
|
|
74
|
+
console.log(` ✗ Calculator.kind should be 'class', got '${calculator.kind}'`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
if (addMethod.kind !== 'method') {
|
|
78
|
+
console.log(` ✗ add.kind should be 'method', got '${addMethod.kind}'`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
if (addMethod.parentClass !== 'Calculator') {
|
|
82
|
+
console.log(` ✗ add.parentClass should be 'Calculator', got '${addMethod.parentClass}'`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
console.log(' ✓ class and methods correct\n');
|
|
86
|
+
// Test 6: Scan directory with both Python and TypeScript
|
|
87
|
+
console.log('Test 6: Scan directory with multiple languages');
|
|
88
|
+
const dirResult = await scanDirectory('testdata', {
|
|
89
|
+
include: ['**/*.py', '**/*.ts'],
|
|
90
|
+
exclude: []
|
|
91
|
+
});
|
|
92
|
+
console.log(` scanned ${dirResult.files.length} files`);
|
|
93
|
+
console.log(` found ${dirResult.totalElements} total elements`);
|
|
94
|
+
const languages = new Set(dirResult.files.map(f => f.language));
|
|
95
|
+
if (!languages.has('python') || !languages.has('typescript')) {
|
|
96
|
+
console.log(' ✗ should have found both Python and TypeScript');
|
|
97
|
+
console.log(' languages:', [...languages]);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
console.log(' ✓ multi-language scan works\n');
|
|
101
|
+
// Print summary of all elements
|
|
102
|
+
console.log('=== Elements Found ===');
|
|
103
|
+
for (const el of result.elements) {
|
|
104
|
+
const prefix = el.parentClass ? `${el.parentClass}.` : '';
|
|
105
|
+
console.log(` [${el.kind}] ${prefix}${el.name}`);
|
|
106
|
+
}
|
|
107
|
+
console.log('\n✓ All TypeScript scanner tests passed');
|
|
108
|
+
}
|
|
109
|
+
runTests().catch(err => {
|
|
110
|
+
console.error('Test failed:', err);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "API Documentation",
|
|
3
|
+
"description": "Generated by skrypt",
|
|
4
|
+
"theme": {
|
|
5
|
+
"primaryColor": "#3b82f6",
|
|
6
|
+
"accentColor": "#8b5cf6"
|
|
7
|
+
},
|
|
8
|
+
"navigation": [
|
|
9
|
+
{
|
|
10
|
+
"group": "Getting Started",
|
|
11
|
+
"pages": [
|
|
12
|
+
{ "title": "Introduction", "path": "/docs" },
|
|
13
|
+
{ "title": "Installation", "path": "/docs/installation" },
|
|
14
|
+
{ "title": "Quick Start", "path": "/docs/quickstart" }
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"group": "API Reference",
|
|
19
|
+
"pages": [
|
|
20
|
+
{ "title": "Overview", "path": "/docs/api" },
|
|
21
|
+
{ "title": "Authentication", "path": "/docs/api/auth" },
|
|
22
|
+
{ "title": "Endpoints", "path": "/docs/api/endpoints" }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"footer": {
|
|
27
|
+
"links": [
|
|
28
|
+
{ "title": "GitHub", "url": "https://github.com" },
|
|
29
|
+
{ "title": "Discord", "url": "https://discord.com" }
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { MDXComponents } from 'mdx/types'
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardGroup,
|
|
5
|
+
Tabs,
|
|
6
|
+
TabList,
|
|
7
|
+
Tab,
|
|
8
|
+
TabPanel,
|
|
9
|
+
CodeGroup,
|
|
10
|
+
CodeBlock,
|
|
11
|
+
HighlightedCode,
|
|
12
|
+
Callout,
|
|
13
|
+
Info,
|
|
14
|
+
Warning,
|
|
15
|
+
Success,
|
|
16
|
+
Error,
|
|
17
|
+
Tip,
|
|
18
|
+
Note,
|
|
19
|
+
Accordion,
|
|
20
|
+
AccordionGroup,
|
|
21
|
+
Steps,
|
|
22
|
+
Step,
|
|
23
|
+
H1,
|
|
24
|
+
H2,
|
|
25
|
+
H3,
|
|
26
|
+
H4,
|
|
27
|
+
ParamTable,
|
|
28
|
+
Schema,
|
|
29
|
+
} from '@/components/mdx'
|
|
30
|
+
|
|
31
|
+
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
|
32
|
+
return {
|
|
33
|
+
// Custom components
|
|
34
|
+
Card,
|
|
35
|
+
CardGroup,
|
|
36
|
+
Tabs,
|
|
37
|
+
TabList,
|
|
38
|
+
Tab,
|
|
39
|
+
TabPanel,
|
|
40
|
+
CodeGroup,
|
|
41
|
+
Callout,
|
|
42
|
+
Info,
|
|
43
|
+
Warning,
|
|
44
|
+
Success,
|
|
45
|
+
Error,
|
|
46
|
+
Tip,
|
|
47
|
+
Note,
|
|
48
|
+
Accordion,
|
|
49
|
+
AccordionGroup,
|
|
50
|
+
Steps,
|
|
51
|
+
Step,
|
|
52
|
+
ParamTable,
|
|
53
|
+
Schema,
|
|
54
|
+
// Override default elements - use syntax-highlighted code
|
|
55
|
+
pre: HighlightedCode,
|
|
56
|
+
h1: H1,
|
|
57
|
+
h2: H2,
|
|
58
|
+
h3: H3,
|
|
59
|
+
h4: H4,
|
|
60
|
+
...components,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/// <reference types="next" />
|
|
2
|
+
/// <reference types="next/image-types/global" />
|
|
3
|
+
/// <reference path="./.next/types/routes.d.ts" />
|
|
4
|
+
|
|
5
|
+
// NOTE: This file should not be edited
|
|
6
|
+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import createMDX from '@next/mdx'
|
|
2
|
+
|
|
3
|
+
const withMDX = createMDX({
|
|
4
|
+
extension: /\.mdx?$/,
|
|
5
|
+
options: {
|
|
6
|
+
remarkPlugins: [],
|
|
7
|
+
rehypePlugins: [],
|
|
8
|
+
},
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
/** @type {import('next').NextConfig} */
|
|
12
|
+
const nextConfig = {
|
|
13
|
+
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
|
|
14
|
+
transpilePackages: ['@scalar/api-reference-react', '@scalar/api-reference'],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default withMDX(nextConfig)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skrypt/template",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev",
|
|
8
|
+
"prebuild": "node scripts/build-search-index.mjs",
|
|
9
|
+
"build": "next build",
|
|
10
|
+
"start": "next start"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"next": "^15.3.0",
|
|
14
|
+
"react": "^19.0.0",
|
|
15
|
+
"react-dom": "^19.0.0",
|
|
16
|
+
"@next/mdx": "^15.3.0",
|
|
17
|
+
"@mdx-js/loader": "^3.1.0",
|
|
18
|
+
"@mdx-js/react": "^3.1.0",
|
|
19
|
+
"next-mdx-remote": "^5.0.0",
|
|
20
|
+
"shiki": "^3.0.0",
|
|
21
|
+
"@orama/orama": "^3.1.0",
|
|
22
|
+
"lucide-react": "^0.500.0",
|
|
23
|
+
"clsx": "^2.1.0",
|
|
24
|
+
"tailwind-merge": "^3.0.0",
|
|
25
|
+
"gray-matter": "^4.0.3",
|
|
26
|
+
"@scalar/nextjs-api-reference": "^0.4.0",
|
|
27
|
+
"@codesandbox/sandpack-react": "^2.20.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.0.0",
|
|
31
|
+
"@types/react": "^19.0.0",
|
|
32
|
+
"@types/react-dom": "^19.0.0",
|
|
33
|
+
"@types/mdx": "^2.0.0",
|
|
34
|
+
"typescript": "^5.8.0",
|
|
35
|
+
"tailwindcss": "^4.1.0",
|
|
36
|
+
"@tailwindcss/postcss": "^4.1.0",
|
|
37
|
+
"postcss": "^8.5.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"internalDocumentIDStore":{"internalIdToId":[]},"index":{"indexes":{"id":{"type":"Radix","node":{"w":"","s":"","e":false,"k":"","d":[],"c":[]},"isArray":false},"title":{"type":"Radix","node":{"w":"","s":"","e":false,"k":"","d":[],"c":[]},"isArray":false},"content":{"type":"Radix","node":{"w":"","s":"","e":false,"k":"","d":[],"c":[]},"isArray":false},"href":{"type":"Radix","node":{"w":"","s":"","e":false,"k":"","d":[],"c":[]},"isArray":false},"section":{"type":"Radix","node":{"w":"","s":"","e":false,"k":"","d":[],"c":[]},"isArray":false}},"vectorIndexes":{},"searchableProperties":["id","title","content","href","section"],"searchablePropertiesWithTypes":{"id":"string","title":"string","content":"string","href":"string","section":"string"},"frequencies":{"id":{},"title":{},"content":{},"href":{},"section":{}},"tokenOccurrences":{"id":{},"title":{},"content":{},"href":{},"section":{}},"avgFieldLength":{"id":0,"title":0,"content":0,"href":0,"section":0},"fieldLengths":{"id":{},"title":{},"content":{},"href":{},"section":{}}},"docs":{"docs":{},"count":0},"sorting":{"language":"english","sortableProperties":["id","title","content","href","section"],"sortablePropertiesWithTypes":{"id":"string","title":"string","content":"string","href":"string","section":"string"},"sorts":{"id":{"docs":{},"orderedDocs":[],"type":"string"},"title":{"docs":{},"orderedDocs":[],"type":"string"},"content":{"docs":{},"orderedDocs":[],"type":"string"},"href":{"docs":{},"orderedDocs":[],"type":"string"},"section":{"docs":{},"orderedDocs":[],"type":"string"}},"enabled":true,"isSorted":true},"pinning":{"rules":[]},"language":"english"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { create, insertMultiple, save } from '@orama/orama'
|
|
2
|
+
import { readdir, readFile, writeFile, mkdir } from 'fs/promises'
|
|
3
|
+
import { join, relative } from 'path'
|
|
4
|
+
import matter from 'gray-matter'
|
|
5
|
+
|
|
6
|
+
const CONTENT_DIR = join(process.cwd(), 'content', 'docs')
|
|
7
|
+
const OUTPUT_DIR = join(process.cwd(), 'public')
|
|
8
|
+
|
|
9
|
+
async function getAllMDXFiles(dir) {
|
|
10
|
+
const files = []
|
|
11
|
+
|
|
12
|
+
async function walk(currentDir) {
|
|
13
|
+
try {
|
|
14
|
+
const entries = await readdir(currentDir, { withFileTypes: true })
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = join(currentDir, entry.name)
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
await walk(fullPath)
|
|
19
|
+
} else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdx')) {
|
|
20
|
+
files.push(fullPath)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
// Directory doesn't exist
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await walk(dir)
|
|
29
|
+
return files
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function extractPlainText(content) {
|
|
33
|
+
return content
|
|
34
|
+
// Remove MDX/JSX components
|
|
35
|
+
.replace(/<[^>]+>/g, '')
|
|
36
|
+
// Remove code blocks
|
|
37
|
+
.replace(/```[\s\S]*?```/g, '')
|
|
38
|
+
.replace(/`[^`]+`/g, '')
|
|
39
|
+
// Remove links but keep text
|
|
40
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
41
|
+
// Remove images
|
|
42
|
+
.replace(/!\[[^\]]*\]\([^)]+\)/g, '')
|
|
43
|
+
// Remove headers markers
|
|
44
|
+
.replace(/^#+\s+/gm, '')
|
|
45
|
+
// Remove emphasis markers
|
|
46
|
+
.replace(/[*_]{1,2}([^*_]+)[*_]{1,2}/g, '$1')
|
|
47
|
+
// Remove horizontal rules
|
|
48
|
+
.replace(/^[-*_]{3,}$/gm, '')
|
|
49
|
+
// Remove extra whitespace
|
|
50
|
+
.replace(/\s+/g, ' ')
|
|
51
|
+
.trim()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getSlugFromPath(filePath) {
|
|
55
|
+
const rel = relative(CONTENT_DIR, filePath)
|
|
56
|
+
const slug = rel
|
|
57
|
+
.replace(/\.(mdx?|md)$/, '')
|
|
58
|
+
.replace(/\/index$/, '')
|
|
59
|
+
.replace(/\\/g, '/')
|
|
60
|
+
|
|
61
|
+
return slug ? `/docs/${slug}` : '/docs'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function buildSearchIndex() {
|
|
65
|
+
console.log('Building search index...')
|
|
66
|
+
|
|
67
|
+
const db = await create({
|
|
68
|
+
schema: {
|
|
69
|
+
id: 'string',
|
|
70
|
+
title: 'string',
|
|
71
|
+
content: 'string',
|
|
72
|
+
href: 'string',
|
|
73
|
+
section: 'string',
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const files = await getAllMDXFiles(CONTENT_DIR)
|
|
78
|
+
const documents = []
|
|
79
|
+
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
try {
|
|
82
|
+
const raw = await readFile(file, 'utf-8')
|
|
83
|
+
const { data, content } = matter(raw)
|
|
84
|
+
|
|
85
|
+
const title = data.title || file.split('/').pop()?.replace(/\.(mdx?|md)$/, '') || 'Untitled'
|
|
86
|
+
const plainContent = extractPlainText(content)
|
|
87
|
+
const href = getSlugFromPath(file)
|
|
88
|
+
const section = data.section || data.category || ''
|
|
89
|
+
|
|
90
|
+
documents.push({
|
|
91
|
+
id: href,
|
|
92
|
+
title,
|
|
93
|
+
content: plainContent.slice(0, 5000), // Limit content size
|
|
94
|
+
href,
|
|
95
|
+
section,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
console.log(` Indexed: ${href}`)
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error(` Error indexing ${file}:`, err.message)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (documents.length > 0) {
|
|
105
|
+
await insertMultiple(db, documents)
|
|
106
|
+
console.log(`\nIndexed ${documents.length} documents`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Export the index
|
|
110
|
+
const data = await save(db)
|
|
111
|
+
await mkdir(OUTPUT_DIR, { recursive: true })
|
|
112
|
+
await writeFile(
|
|
113
|
+
join(OUTPUT_DIR, 'search-index.json'),
|
|
114
|
+
JSON.stringify(data)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
console.log('Search index saved to public/search-index.json')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
buildSearchIndex().catch(console.error)
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
4
|
+
import YAML from 'yaml'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Mock Server API Route
|
|
8
|
+
* Returns example responses from OpenAPI spec for any endpoint
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface OpenAPISpec {
|
|
12
|
+
paths?: Record<string, Record<string, PathOperation>>
|
|
13
|
+
components?: {
|
|
14
|
+
schemas?: Record<string, any>
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface PathOperation {
|
|
19
|
+
responses?: Record<string, {
|
|
20
|
+
description?: string
|
|
21
|
+
content?: Record<string, {
|
|
22
|
+
schema?: any
|
|
23
|
+
example?: any
|
|
24
|
+
examples?: Record<string, { value: any }>
|
|
25
|
+
}>
|
|
26
|
+
}>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function loadOpenAPISpec(): Promise<OpenAPISpec | null> {
|
|
30
|
+
const specPaths = [
|
|
31
|
+
'openapi.json',
|
|
32
|
+
'openapi.yaml',
|
|
33
|
+
'openapi.yml',
|
|
34
|
+
'swagger.json',
|
|
35
|
+
'swagger.yaml',
|
|
36
|
+
'swagger.yml',
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
for (const specPath of specPaths) {
|
|
40
|
+
try {
|
|
41
|
+
const fullPath = join(process.cwd(), 'content', specPath)
|
|
42
|
+
const content = await readFile(fullPath, 'utf-8')
|
|
43
|
+
const isJson = specPath.endsWith('.json')
|
|
44
|
+
return isJson ? JSON.parse(content) : YAML.parse(content)
|
|
45
|
+
} catch {
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generate fake data based on JSON Schema
|
|
55
|
+
*/
|
|
56
|
+
function generateFromSchema(schema: any, components?: any, depth = 0): any {
|
|
57
|
+
if (depth > 5) return null // Prevent infinite recursion
|
|
58
|
+
|
|
59
|
+
if (!schema) return null
|
|
60
|
+
|
|
61
|
+
// Handle $ref
|
|
62
|
+
if (schema.$ref) {
|
|
63
|
+
const refPath = schema.$ref.replace('#/components/schemas/', '')
|
|
64
|
+
const refSchema = components?.schemas?.[refPath]
|
|
65
|
+
return refSchema ? generateFromSchema(refSchema, components, depth + 1) : null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Handle allOf, oneOf, anyOf
|
|
69
|
+
if (schema.allOf) {
|
|
70
|
+
const merged = schema.allOf.reduce((acc: any, s: any) => ({
|
|
71
|
+
...acc,
|
|
72
|
+
...generateFromSchema(s, components, depth + 1)
|
|
73
|
+
}), {})
|
|
74
|
+
return merged
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (schema.oneOf || schema.anyOf) {
|
|
78
|
+
const options = schema.oneOf || schema.anyOf
|
|
79
|
+
return generateFromSchema(options[0], components, depth + 1)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Use example if provided
|
|
83
|
+
if (schema.example !== undefined) return schema.example
|
|
84
|
+
if (schema.default !== undefined) return schema.default
|
|
85
|
+
|
|
86
|
+
// Generate based on type
|
|
87
|
+
switch (schema.type) {
|
|
88
|
+
case 'string':
|
|
89
|
+
if (schema.enum) return schema.enum[0]
|
|
90
|
+
if (schema.format === 'date') return '2024-01-15'
|
|
91
|
+
if (schema.format === 'date-time') return '2024-01-15T10:30:00Z'
|
|
92
|
+
if (schema.format === 'email') return 'user@example.com'
|
|
93
|
+
if (schema.format === 'uri') return 'https://example.com'
|
|
94
|
+
if (schema.format === 'uuid') return '550e8400-e29b-41d4-a716-446655440000'
|
|
95
|
+
return 'string'
|
|
96
|
+
|
|
97
|
+
case 'number':
|
|
98
|
+
case 'integer':
|
|
99
|
+
if (schema.minimum !== undefined) return schema.minimum
|
|
100
|
+
if (schema.maximum !== undefined) return schema.maximum
|
|
101
|
+
return schema.type === 'integer' ? 42 : 3.14
|
|
102
|
+
|
|
103
|
+
case 'boolean':
|
|
104
|
+
return true
|
|
105
|
+
|
|
106
|
+
case 'array':
|
|
107
|
+
const itemSchema = schema.items
|
|
108
|
+
const item = generateFromSchema(itemSchema, components, depth + 1)
|
|
109
|
+
return item ? [item] : []
|
|
110
|
+
|
|
111
|
+
case 'object':
|
|
112
|
+
const obj: Record<string, any> = {}
|
|
113
|
+
const properties = schema.properties || {}
|
|
114
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
115
|
+
obj[key] = generateFromSchema(propSchema, components, depth + 1)
|
|
116
|
+
}
|
|
117
|
+
return obj
|
|
118
|
+
|
|
119
|
+
default:
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get response for a path and method
|
|
126
|
+
*/
|
|
127
|
+
function getResponse(spec: OpenAPISpec, path: string, method: string): any {
|
|
128
|
+
const pathItem = spec.paths?.[path]
|
|
129
|
+
if (!pathItem) {
|
|
130
|
+
// Try with path parameters replaced
|
|
131
|
+
for (const [pathPattern, item] of Object.entries(spec.paths || {})) {
|
|
132
|
+
const regex = new RegExp('^' + pathPattern.replace(/\{[^}]+\}/g, '[^/]+') + '$')
|
|
133
|
+
if (regex.test(path)) {
|
|
134
|
+
const operation = item[method.toLowerCase()]
|
|
135
|
+
if (operation) {
|
|
136
|
+
return extractResponse(operation, spec.components)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const operation = pathItem[method.toLowerCase()]
|
|
144
|
+
return operation ? extractResponse(operation, spec.components) : null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function extractResponse(operation: PathOperation, components?: any): any {
|
|
148
|
+
const responses = operation.responses
|
|
149
|
+
if (!responses) return null
|
|
150
|
+
|
|
151
|
+
// Prefer 200, then 201, then first 2xx
|
|
152
|
+
const statusCodes = ['200', '201', ...Object.keys(responses).filter(c => c.startsWith('2'))]
|
|
153
|
+
|
|
154
|
+
for (const code of statusCodes) {
|
|
155
|
+
const response = responses[code]
|
|
156
|
+
if (!response?.content) continue
|
|
157
|
+
|
|
158
|
+
// Prefer application/json
|
|
159
|
+
const content = response.content['application/json'] || Object.values(response.content)[0]
|
|
160
|
+
if (!content) continue
|
|
161
|
+
|
|
162
|
+
// Use example if available
|
|
163
|
+
if (content.example) return content.example
|
|
164
|
+
if (content.examples) {
|
|
165
|
+
const firstExample = Object.values(content.examples)[0]
|
|
166
|
+
if (firstExample?.value) return firstExample.value
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Generate from schema
|
|
170
|
+
if (content.schema) {
|
|
171
|
+
return generateFromSchema(content.schema, components)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Handler for all HTTP methods
|
|
179
|
+
async function handler(
|
|
180
|
+
req: NextRequest,
|
|
181
|
+
{ params }: { params: Promise<{ path: string[] }> }
|
|
182
|
+
) {
|
|
183
|
+
const spec = await loadOpenAPISpec()
|
|
184
|
+
|
|
185
|
+
if (!spec) {
|
|
186
|
+
return NextResponse.json(
|
|
187
|
+
{ error: 'OpenAPI specification not found', mock: true },
|
|
188
|
+
{ status: 404 }
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const { path: pathSegments } = await params
|
|
193
|
+
const path = '/' + pathSegments.join('/')
|
|
194
|
+
const method = req.method
|
|
195
|
+
|
|
196
|
+
const response = getResponse(spec, path, method)
|
|
197
|
+
|
|
198
|
+
if (response === null) {
|
|
199
|
+
return NextResponse.json(
|
|
200
|
+
{
|
|
201
|
+
error: `No mock response found for ${method} ${path}`,
|
|
202
|
+
mock: true,
|
|
203
|
+
hint: 'Ensure the path exists in your OpenAPI spec with example responses'
|
|
204
|
+
},
|
|
205
|
+
{ status: 404 }
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Simulate network delay
|
|
210
|
+
await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 200))
|
|
211
|
+
|
|
212
|
+
return NextResponse.json(response, {
|
|
213
|
+
headers: {
|
|
214
|
+
'X-Mock-Response': 'true',
|
|
215
|
+
'X-Response-Time': `${Math.floor(100 + Math.random() * 200)}ms`,
|
|
216
|
+
},
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const GET = handler
|
|
221
|
+
export const POST = handler
|
|
222
|
+
export const PUT = handler
|
|
223
|
+
export const PATCH = handler
|
|
224
|
+
export const DELETE = handler
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import { NextResponse } from 'next/server'
|
|
4
|
+
|
|
5
|
+
async function getOpenAPISpec() {
|
|
6
|
+
const specPaths = [
|
|
7
|
+
'openapi.json',
|
|
8
|
+
'openapi.yaml',
|
|
9
|
+
'openapi.yml',
|
|
10
|
+
'swagger.json',
|
|
11
|
+
'swagger.yaml',
|
|
12
|
+
'swagger.yml',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
for (const specPath of specPaths) {
|
|
16
|
+
try {
|
|
17
|
+
const fullPath = join(process.cwd(), 'content', specPath)
|
|
18
|
+
const content = await readFile(fullPath, 'utf-8')
|
|
19
|
+
const isJson = specPath.endsWith('.json')
|
|
20
|
+
return { content, isJson }
|
|
21
|
+
} catch {
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function GET() {
|
|
30
|
+
const result = await getOpenAPISpec()
|
|
31
|
+
|
|
32
|
+
if (!result) {
|
|
33
|
+
return NextResponse.json(
|
|
34
|
+
{ error: 'OpenAPI specification not found' },
|
|
35
|
+
{ status: 404 }
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (result.isJson) {
|
|
40
|
+
return NextResponse.json(JSON.parse(result.content))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return new NextResponse(result.content, {
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': 'text/yaml',
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
}
|