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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/autofix/index.d.ts +46 -0
  4. package/dist/autofix/index.js +240 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +40 -0
  7. package/dist/commands/autofix.d.ts +2 -0
  8. package/dist/commands/autofix.js +143 -0
  9. package/dist/commands/generate.d.ts +2 -0
  10. package/dist/commands/generate.js +320 -0
  11. package/dist/commands/init.d.ts +2 -0
  12. package/dist/commands/init.js +56 -0
  13. package/dist/commands/review-pr.d.ts +2 -0
  14. package/dist/commands/review-pr.js +117 -0
  15. package/dist/commands/watch.d.ts +2 -0
  16. package/dist/commands/watch.js +142 -0
  17. package/dist/config/index.d.ts +2 -0
  18. package/dist/config/index.js +2 -0
  19. package/dist/config/loader.d.ts +9 -0
  20. package/dist/config/loader.js +82 -0
  21. package/dist/config/types.d.ts +24 -0
  22. package/dist/config/types.js +34 -0
  23. package/dist/generator/generator.d.ts +15 -0
  24. package/dist/generator/generator.js +144 -0
  25. package/dist/generator/index.d.ts +4 -0
  26. package/dist/generator/index.js +4 -0
  27. package/dist/generator/organizer.d.ts +29 -0
  28. package/dist/generator/organizer.js +222 -0
  29. package/dist/generator/types.d.ts +83 -0
  30. package/dist/generator/types.js +1 -0
  31. package/dist/generator/writer.d.ts +28 -0
  32. package/dist/generator/writer.js +320 -0
  33. package/dist/github/pr-comments.d.ts +40 -0
  34. package/dist/github/pr-comments.js +308 -0
  35. package/dist/llm/anthropic-client.d.ts +16 -0
  36. package/dist/llm/anthropic-client.js +92 -0
  37. package/dist/llm/index.d.ts +53 -0
  38. package/dist/llm/index.js +400 -0
  39. package/dist/llm/llm.manual-test.d.ts +1 -0
  40. package/dist/llm/llm.manual-test.js +112 -0
  41. package/dist/llm/llm.mock-test.d.ts +4 -0
  42. package/dist/llm/llm.mock-test.js +79 -0
  43. package/dist/llm/openai-client.d.ts +17 -0
  44. package/dist/llm/openai-client.js +90 -0
  45. package/dist/llm/types.d.ts +60 -0
  46. package/dist/llm/types.js +20 -0
  47. package/dist/scanner/content-type.d.ts +39 -0
  48. package/dist/scanner/content-type.js +194 -0
  49. package/dist/scanner/content-type.test.d.ts +1 -0
  50. package/dist/scanner/content-type.test.js +231 -0
  51. package/dist/scanner/go.d.ts +20 -0
  52. package/dist/scanner/go.js +269 -0
  53. package/dist/scanner/index.d.ts +21 -0
  54. package/dist/scanner/index.js +137 -0
  55. package/dist/scanner/python.d.ts +6 -0
  56. package/dist/scanner/python.js +57 -0
  57. package/dist/scanner/python_parser.py +230 -0
  58. package/dist/scanner/rust.d.ts +23 -0
  59. package/dist/scanner/rust.js +304 -0
  60. package/dist/scanner/scanner.test.d.ts +1 -0
  61. package/dist/scanner/scanner.test.js +210 -0
  62. package/dist/scanner/types.d.ts +50 -0
  63. package/dist/scanner/types.js +1 -0
  64. package/dist/scanner/typescript.d.ts +34 -0
  65. package/dist/scanner/typescript.js +327 -0
  66. package/dist/scanner/typescript.manual-test.d.ts +1 -0
  67. package/dist/scanner/typescript.manual-test.js +112 -0
  68. package/dist/template/docs.json +32 -0
  69. package/dist/template/mdx-components.tsx +62 -0
  70. package/dist/template/next-env.d.ts +6 -0
  71. package/dist/template/next.config.mjs +17 -0
  72. package/dist/template/package.json +39 -0
  73. package/dist/template/postcss.config.mjs +5 -0
  74. package/dist/template/public/search-index.json +1 -0
  75. package/dist/template/scripts/build-search-index.mjs +120 -0
  76. package/dist/template/src/app/api/mock/[...path]/route.ts +224 -0
  77. package/dist/template/src/app/api/openapi/route.ts +48 -0
  78. package/dist/template/src/app/api/rate-limit/route.ts +84 -0
  79. package/dist/template/src/app/docs/[...slug]/page.tsx +81 -0
  80. package/dist/template/src/app/docs/layout.tsx +9 -0
  81. package/dist/template/src/app/docs/page.mdx +67 -0
  82. package/dist/template/src/app/error.tsx +63 -0
  83. package/dist/template/src/app/layout.tsx +71 -0
  84. package/dist/template/src/app/page.tsx +18 -0
  85. package/dist/template/src/app/reference/route.ts +36 -0
  86. package/dist/template/src/app/robots.ts +14 -0
  87. package/dist/template/src/app/sitemap.ts +64 -0
  88. package/dist/template/src/components/breadcrumbs.tsx +41 -0
  89. package/dist/template/src/components/copy-button.tsx +29 -0
  90. package/dist/template/src/components/docs-layout.tsx +35 -0
  91. package/dist/template/src/components/edit-link.tsx +39 -0
  92. package/dist/template/src/components/feedback.tsx +52 -0
  93. package/dist/template/src/components/header.tsx +66 -0
  94. package/dist/template/src/components/mdx/accordion.tsx +48 -0
  95. package/dist/template/src/components/mdx/api-badge.tsx +57 -0
  96. package/dist/template/src/components/mdx/callout.tsx +111 -0
  97. package/dist/template/src/components/mdx/card.tsx +62 -0
  98. package/dist/template/src/components/mdx/changelog.tsx +57 -0
  99. package/dist/template/src/components/mdx/code-block.tsx +42 -0
  100. package/dist/template/src/components/mdx/code-group.tsx +125 -0
  101. package/dist/template/src/components/mdx/code-playground.tsx +322 -0
  102. package/dist/template/src/components/mdx/go-playground.tsx +235 -0
  103. package/dist/template/src/components/mdx/heading.tsx +37 -0
  104. package/dist/template/src/components/mdx/highlighted-code.tsx +89 -0
  105. package/dist/template/src/components/mdx/index.tsx +15 -0
  106. package/dist/template/src/components/mdx/param-table.tsx +71 -0
  107. package/dist/template/src/components/mdx/python-playground.tsx +293 -0
  108. package/dist/template/src/components/mdx/steps.tsx +43 -0
  109. package/dist/template/src/components/mdx/tabs.tsx +81 -0
  110. package/dist/template/src/components/rate-limit-display.tsx +183 -0
  111. package/dist/template/src/components/search-dialog.tsx +178 -0
  112. package/dist/template/src/components/sidebar.tsx +129 -0
  113. package/dist/template/src/components/syntax-theme-selector.tsx +50 -0
  114. package/dist/template/src/components/table-of-contents.tsx +84 -0
  115. package/dist/template/src/components/theme-toggle.tsx +46 -0
  116. package/dist/template/src/components/version-selector.tsx +61 -0
  117. package/dist/template/src/contexts/syntax-theme.tsx +52 -0
  118. package/dist/template/src/lib/highlight.ts +83 -0
  119. package/dist/template/src/lib/search-types.ts +37 -0
  120. package/dist/template/src/lib/search.ts +125 -0
  121. package/dist/template/src/lib/utils.ts +6 -0
  122. package/dist/template/src/styles/globals.css +152 -0
  123. package/dist/template/tsconfig.json +25 -0
  124. package/dist/template/tsconfig.tsbuildinfo +1 -0
  125. 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,5 @@
1
+ export default {
2
+ plugins: {
3
+ '@tailwindcss/postcss': {},
4
+ },
5
+ }
@@ -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
+ }